/*-------------------------------------------------------------------------
 *
 * dependency.c
 *	  支持对象间依赖关系的常规例程。
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *	  src/backend/catalog/dependency.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/genam.h"
#include "access/htup_details.h"
#include "access/table.h"
#include "access/xact.h"
#include "catalog/catalog.h"
#include "catalog/dependency.h"
#include "catalog/heap.h"
#include "catalog/index.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
#include "catalog/pg_amop.h"
#include "catalog/pg_amproc.h"
#include "catalog/pg_attrdef.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_cast.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_conversion.h"
#include "catalog/pg_database.h"
#include "catalog/pg_default_acl.h"
#include "catalog/pg_depend.h"
#include "catalog/pg_event_trigger.h"
#include "catalog/pg_extension.h"
#include "catalog/pg_foreign_data_wrapper.h"
#include "catalog/pg_foreign_server.h"
#include "catalog/pg_init_privs.h"
#include "catalog/pg_language.h"
#include "catalog/pg_largeobject.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_opfamily.h"
#include "catalog/pg_parameter_acl.h"
#include "catalog/pg_policy.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_subscription.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_transform.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_ts_config.h"
#include "catalog/pg_ts_dict.h"
#include "catalog/pg_ts_parser.h"
#include "catalog/pg_ts_template.h"
#include "catalog/pg_type.h"
#include "catalog/pg_user_mapping.h"
#include "commands/comment.h"
#include "commands/defrem.h"
#include "commands/event_trigger.h"
#include "commands/extension.h"
#include "commands/policy.h"
#include "commands/publicationcmds.h"
#include "commands/seclabel.h"
#include "commands/sequence.h"
#include "commands/trigger.h"
#include "commands/typecmds.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "parser/parsetree.h"
#include "rewrite/rewriteRemove.h"
#include "storage/lmgr.h"
#include "utils/acl.h"
#include "utils/fmgroids.h"
#include "utils/guc.h"
#include "utils/lsyscache.h"
#include "utils/syscache.h"


/*
 * 删除处理需要为每个计划删除的 ObjectAddress 额外的状态。为了简单和代码共享，我们使 ObjectAddresses 代码支持带或不带此额外状态的数组。
 */
typedef struct
{
	int			flags;			/* 位掩码，见下面的位定义 */
	ObjectAddress dependee;		/* 删除此对象的对象 */
} ObjectAddressExtra;

/* ObjectAddressExtra 标志位 */
#define DEPFLAG_ORIGINAL	0x0001	/* 原始删除目标 */
#define DEPFLAG_NORMAL		0x0002	/* 通过正常依赖达到 */
#define DEPFLAG_AUTO		0x0004	/* 通过自动依赖达到 */
#define DEPFLAG_INTERNAL	0x0008	/* 通过内部依赖达到 */
#define DEPFLAG_PARTITION	0x0010	/* 通过分区依赖达到 */
#define DEPFLAG_EXTENSION	0x0020	/* 通过扩展依赖达到 */
#define DEPFLAG_REVERSE		0x0040	/* 反向内部/扩展链接 */
#define DEPFLAG_IS_PART		0x0080	/* 有分区依赖 */
#define DEPFLAG_SUBOBJECT	0x0100	/* 另一个可删除对象的子对象 */


/* 可扩展的 ObjectAddresses 列表 */
struct ObjectAddresses
{
	ObjectAddress *refs;		/* => palloc 的数组 */
	ObjectAddressExtra *extras; /* => palloc 的数组，或者在未使用时为 NULL */
	int			numrefs;		/* 当前引用数量 */
	int			maxrefs;		/* palloc 数组的当前大小 */
};

/* typedef ObjectAddresses 出现在 dependency.h 中 */

/* ObjectAddresses 的线程列表，用于递归检测 */
typedef struct ObjectAddressStack
{
	const ObjectAddress *object;	/* 被访问的对象 */
	int			flags;			/* 它的当前标志位 */
	struct ObjectAddressStack *next;	/* 下一个外部堆栈级别 */
} ObjectAddressStack;

/* findDependentObjects 中的临时存储 */
typedef struct
{
	ObjectAddress obj;			/* 要删除的对象 --- 必须是第一个 */
	int			subflags;		/* 在递归到对象时传递的标志 */
} ObjectAddressAndFlags;

/* 用于 find_expr_references_walker */
typedef struct
{
	ObjectAddresses *addrs;		/* 正在积累的地址 */
	List	   *rtables;		/* 用于解析 Vars 的 rangetable 列表 */
} find_expr_references_context;

/*
 * 此常量表将 ObjectClasses 映射到相应的目录 OIDs。
 * 请参阅 getObjectClass()。
 */
static const Oid object_classes[] = {
	RelationRelationId,			/* OCLASS_CLASS */
	ProcedureRelationId,		/* OCLASS_PROC */
	TypeRelationId,				/* OCLASS_TYPE */
	CastRelationId,				/* OCLASS_CAST */  
	CollationRelationId,		/* OCLASS_COLLATION */  
	ConstraintRelationId,		/* OCLASS_CONSTRAINT */  
	ConversionRelationId,		/* OCLASS_CONVERSION */  
	AttrDefaultRelationId,		/* OCLASS_DEFAULT */  
	LanguageRelationId,			/* OCLASS_LANGUAGE */  
	LargeObjectRelationId,		/* OCLASS_LARGEOBJECT */  
	OperatorRelationId,			/* OCLASS_OPERATOR */  
	OperatorClassRelationId,	/* OCLASS_OPCLASS */  
	OperatorFamilyRelationId,	/* OCLASS_OPFAMILY */  
	AccessMethodRelationId,		/* OCLASS_AM */  
	AccessMethodOperatorRelationId, /* OCLASS_AMOP */  
	AccessMethodProcedureRelationId,	/* OCLASS_AMPROC */  
	RewriteRelationId,			/* OCLASS_REWRITE */  
	TriggerRelationId,			/* OCLASS_TRIGGER */  
	NamespaceRelationId,		/* OCLASS_SCHEMA */  
	StatisticExtRelationId,		/* OCLASS_STATISTIC_EXT */  
	TSParserRelationId,			/* OCLASS_TSPARSER */  
	TSDictionaryRelationId,		/* OCLASS_TSDICT */  
	TSTemplateRelationId,		/* OCLASS_TSTEMPLATE */  
	TSConfigRelationId,			/* OCLASS_TSCONFIG */  
	AuthIdRelationId,			/* OCLASS_ROLE */  
	DatabaseRelationId,			/* OCLASS_DATABASE */  
	TableSpaceRelationId,		/* OCLASS_TBLSPACE */  
	ForeignDataWrapperRelationId,	/* OCLASS_FDW */  
	ForeignServerRelationId,	/* OCLASS_FOREIGN_SERVER */  
	UserMappingRelationId,		/* OCLASS_USER_MAPPING */  
	DefaultAclRelationId,		/* OCLASS_DEFACL */  
	ExtensionRelationId,		/* OCLASS_EXTENSION */  
	EventTriggerRelationId,		/* OCLASS_EVENT_TRIGGER */  
	ParameterAclRelationId,		/* OCLASS_PARAMETER_ACL */  
	PolicyRelationId,			/* OCLASS_POLICY */  
	PublicationNamespaceRelationId, /* OCLASS_PUBLICATION_NAMESPACE */  
	PublicationRelationId,		/* OCLASS_PUBLICATION */  
	PublicationRelRelationId,	/* OCLASS_PUBLICATION_REL */  
	SubscriptionRelationId,		/* OCLASS_SUBSCRIPTION */  
	TransformRelationId			/* OCLASS_TRANSFORM */  
};


static void fc_findDependentObjects(const ObjectAddress *fc_object,
								 int fc_objflags,
								 int fc_flags,
								 ObjectAddressStack *fc_stack,
								 ObjectAddresses *fc_targetObjects,
								 const ObjectAddresses *fc_pendingObjects,
								 Relation *fc_depRel);
static void fc_reportDependentObjects(const ObjectAddresses *fc_targetObjects,
								   DropBehavior fc_behavior,
								   int fc_flags,
								   const ObjectAddress *fc_origObject);
static void fc_deleteOneObject(const ObjectAddress *fc_object,
							Relation *fc_depRel, int32 fc_flags);
static void fc_doDeletion(const ObjectAddress *fc_object, int fc_flags);
static bool fc_find_expr_references_walker(Node *fc_node,
										find_expr_references_context *fc_context);
static void fc_process_function_rte_ref(RangeTblEntry *fc_rte, AttrNumber fc_attnum,
									 find_expr_references_context *fc_context);
static void fc_eliminate_duplicate_dependencies(ObjectAddresses *fc_addrs);
static int	fc_object_address_comparator(const void *fc_a, const void *fc_b);
static void add_object_address(ObjectClass fc_oclass, Oid fc_objectId, int32 fc_subId,
							   ObjectAddresses *fc_addrs);
static void add_exact_object_address_extra(const ObjectAddress *fc_object,
										   const ObjectAddressExtra *fc_extra,
										   ObjectAddresses *fc_addrs);
static bool fc_object_address_present_add_flags(const ObjectAddress *fc_object,
											 int fc_flags,
											 ObjectAddresses *fc_addrs);
static bool fc_stack_address_present_add_flags(const ObjectAddress *fc_object,
											int fc_flags,
											ObjectAddressStack *fc_stack);
static void fc_DeleteInitPrivs(const ObjectAddress *fc_object);


/*  
 * 遍历给定的对象，执行最终操作，并执行  
 * 实际删除。  
 */  
static void fc_deleteObjectsInList(ObjectAddresses *fc_targetObjects, Relation *fc_depRel,
					int fc_flags)
{
	int			fc_i;

	/*  
	 * 根据需要跟踪事件触发器的对象。  
	 */  
	if (trackDroppedObjectsNeeded() && !(fc_flags & PERFORM_DELETION_INTERNAL))
	{
		for (fc_i = 0; fc_i < fc_targetObjects->numrefs; fc_i++)
		{
			const ObjectAddress *fc_thisobj = &fc_targetObjects->refs[fc_i];
			const ObjectAddressExtra *fc_extra = &fc_targetObjects->extras[fc_i];
			bool		fc_original = false;
			bool		fc_normal = false;

			if (fc_extra->flags & DEPFLAG_ORIGINAL)
				fc_original = true;
			if (fc_extra->flags & DEPFLAG_NORMAL)
				fc_normal = true;
			if (fc_extra->flags & DEPFLAG_REVERSE)
				fc_normal = true;

			if (EventTriggerSupportsObjectClass(getObjectClass(fc_thisobj)))
			{
				EventTriggerSQLDropAddObject(fc_thisobj, fc_original, fc_normal);
			}
		}
	}

	/*  
	 * 按正确顺序删除所有对象，但如果被告知，  
	 * 应跳过原始对象。  
	 */  
	for (fc_i = 0; fc_i < fc_targetObjects->numrefs; fc_i++)
	{
		ObjectAddress *fc_thisobj = fc_targetObjects->refs + fc_i;
		ObjectAddressExtra *fc_thisextra = fc_targetObjects->extras + fc_i;

		if ((fc_flags & PERFORM_DELETION_SKIP_ORIGINAL) &&
			(fc_thisextra->flags & DEPFLAG_ORIGINAL))
			continue;

		fc_deleteOneObject(fc_thisobj, fc_depRel, fc_flags);
	}
}

/*  
 * performDeletion: 尝试删除指定对象。如果指定了CASCADE  
 * 行为，还要递归删除任何依赖对象。  
 * 如果指定了RESTRICT行为，如果有任何依赖对象，则会出错，  
 * 除非那些根据依赖类型应隐式删除的对象。  
 *  
 * 这是所有形式的DROP的外部控制例程，这些DROP  
 * 对象可以参与依赖关系。注意，performMultipleDeletions  
 * 是同一主题的变体；如果您对此进行更改，您可能需要  
 * 解决这个。  
 *  
 * flags 参数中的位可以包括：  
 *  
 * PERFORM_DELETION_INTERNAL: 表示删除操作不是  
 * 用户发起操作的直接结果。例如，当临时  
 * 模式被清理以供新后端使用，或当列  
 * 默认值被删除作为添加新默认值的中间步骤，这被  
 * 视为内部操作。另一方面，当我们删除某样东西  
 * 是因为用户发出DROP语句，这不是内部操作。  
 * 目前这会抑制调用事件触发器和进行某些权限检查。  
 *  
 * PERFORM_DELETION_CONCURRENTLY: 以并发方式执行删除。  
 * 目前这仅适用于删除索引；不要为其他对象类型设置，  
 * 否则您可能会得到奇怪的结果。  
 *  
 * PERFORM_DELETION_QUIETLY: 将消息级别从NOTICE降低到DEBUG2。  
 *  
 * PERFORM_DELETION_SKIP_ORIGINAL: 不删除指定的对象，  
 * 而只删除依赖于它/它们的对象。  
 *  
 * PERFORM_DELETION_SKIP_EXTENSIONS: 不删除扩展，即使在  
 * 删除属于扩展的对象时也是如此。通常只应在  
 * 删除临时对象时使用。  
 *  
 * PERFORM_DELETION_CONCURRENT_LOCK: 正常执行删除操作，但  
 * 以锁定的方式，仿佛是并发的。这用于REINDEX CONCURRENTLY。  
 */  
void performDeletion(const ObjectAddress *fc_object,
				DropBehavior fc_behavior, int fc_flags)
{
	Relation	fc_depRel;
	ObjectAddresses *fc_targetObjects;

	/*  
	 * 通过仅打开pg_depend一次并将  
	 * Relation指针传递给所有递归删除步骤，我们节省了一些循环。  
	 */  
	fc_depRel = table_open(DependRelationId, RowExclusiveLock);

	/*  
	 * 在目标对象上获得删除锁。 （理想情况下，调用者  
	 * 已经完成了此操作，但许多地方对此不够严谨。）  
	 */  
	AcquireDeletionLock(fc_object, 0);

	/*  
	 * 构建要删除的对象列表（即，给定对象加上  
	 * 所有直接或间接依赖于它的对象）。  
	 */  
	fc_targetObjects = new_object_addresses();

	fc_findDependentObjects(fc_object,
						 DEPFLAG_ORIGINAL,
						 fc_flags,
						 NULL,	/* 空堆栈 */  
						 fc_targetObjects,
						 NULL,	/* 没有待处理对象 */  
						 &fc_depRel);

	/*  
	 * 检查是否允许删除，并报告级联删除的情况。  
	 */  
	fc_reportDependentObjects(fc_targetObjects,
						   fc_behavior,
						   fc_flags,
						   fc_object);

	/* 执行删除 */  
	fc_deleteObjectsInList(fc_targetObjects, &fc_depRel, fc_flags);

	/* 并进行清理 */
	free_object_addresses(fc_targetObjects);

	table_close(fc_depRel, RowExclusiveLock);
}

/*  
 * performMultipleDeletions: 类似于performDeletion，但一次对多个  
 * 对象进行操作。  
 *  
 * 与发出多个performDeletion调用的主要区别在于，  
 * 对于每个要删除的对象，隐式删除的对象列表是   
 * 所有对象隐式对象列表的并集。这使得每个检查都更放松。  
 */  
void performMultipleDeletions(const ObjectAddresses *fc_objects,
						 DropBehavior fc_behavior, int fc_flags)
{
	Relation	fc_depRel;
	ObjectAddresses *fc_targetObjects;
	int			fc_i;

	/* 如果没有对象，则没有工作... */  
	if (fc_objects->numrefs <= 0)
		return;

	/*  
	 * 通过仅打开pg_depend一次并将  
	 * Relation指针传递给所有递归删除步骤，我们节省了一些循环。  
	 */  
	fc_depRel = table_open(DependRelationId, RowExclusiveLock);

	/*  
	 * 构建要删除的对象列表（即，给定对象加上  
	 * 所有直接或间接依赖于它们的对象）。请注意，由于  
	 * 我们将整个对象列表作为待处理对象上下文传递，  
	 * 我们不会因为尝试删除一个在列表中内部依赖于  
	 * 另一个对象而失败；我们只会跳过该对象，并且  
	 * 在到达其所有者时删除它。  
	 */  
	fc_targetObjects = new_object_addresses();

	for (fc_i = 0; fc_i < fc_objects->numrefs; fc_i++)
	{
		const ObjectAddress *fc_thisobj = fc_objects->refs + fc_i;

		/*  
		 * 在每个目标对象上获得删除锁。 （理想情况下，调用者  
		 * 已经完成了此操作，但许多地方对此不够严谨。）  
		 */  
		AcquireDeletionLock(fc_thisobj, fc_flags);

		fc_findDependentObjects(fc_thisobj,
							 DEPFLAG_ORIGINAL,
							 fc_flags,
							 NULL,	/* 空堆栈 */  
							 fc_targetObjects,
							 fc_objects,
							 &fc_depRel);
	}

	/*  
	 * 检查是否允许删除，并报告级联删除的情况。  
	 *  
	 * 如果要删除的对象正好是一个，像在performDeletion()中一样  
	 * 报告它，否则我们必须更加模糊。  
	 */  
	fc_reportDependentObjects(fc_targetObjects,
						   fc_behavior,
						   fc_flags,
						   (fc_objects->numrefs == 1 ? fc_objects->refs : NULL));

	/* 执行删除 */  
	fc_deleteObjectsInList(fc_targetObjects, &fc_depRel, fc_flags);

	/* 并进行清理 */
	free_object_addresses(fc_targetObjects);

	table_close(fc_depRel, RowExclusiveLock);
}


/*
 * findDependentObjects - 查找所有依赖于 'object' 的对象
 *
 * 对于每个依赖于起始对象的对象，获取该对象的删除锁，将其添加到
 * targetObjects（如果还没有在其中），并递归查找依赖于它的对象。
 * 对象的依赖关系将在对象本身之前放入 targetObjects；这意味着
 * 完成的列表顺序表示安全的删除顺序。
 *
 * 调用者必须已经对 'object' 本身获取了删除锁，
 * 但不得将其添加到 targetObjects。 （注意：有一些特殊情况
 * 我们也不会添加该对象，并且还会释放调用者的锁。这有点丑陋，
 * 但 API 的设置方式是为了在锁定对象后方便重新检查其存活性。
 * 请参阅函数内的说明。）
 *
 * 在删除整个对象时（subId = 0），我们也会查找其子对象的依赖关系。
 *
 *	object: 要添加到 targetObjects 并查找依赖关系的对象
 *	objflags: 要或进对象的 targetObjects 条目的标志
 *	flags: 整个删除操作的 PERFORM_DELETION_xxx 标志
 *	stack: 当前递归中访问的对象列表；最上面的项目是
 *		 我们递归的对象（对于外部调用者为 NULL）
 *	targetObjects: 计划删除的对象列表
 *	pendingObjects: 其他计划被销毁的对象列表，
 *		 但不一定在 targetObjects 中（如果没有可以为 NULL）
 *	*depRel: 已打开的 pg_depend 关系
 *
 * 注意：objflags 描述了此时访问此特定对象的原因，
 * 并在递归时不向下传递。flags 参数是向下传递的，
 * 因为它描述了我们整体在做什么。
 */
static void fc_findDependentObjects(const ObjectAddress *fc_object,
					 int fc_objflags,
					 int fc_flags,
					 ObjectAddressStack *fc_stack,
					 ObjectAddresses *fc_targetObjects,
					 const ObjectAddresses *fc_pendingObjects,
					 Relation *fc_depRel)
{
	ScanKeyData fc_key[3];
	int			fc_nkeys;
	SysScanDesc fc_scan;
	HeapTuple	fc_tup;
	ObjectAddress fc_otherObject;
	ObjectAddress fc_owningObject;
	ObjectAddress fc_partitionObject;
	ObjectAddressAndFlags *fc_dependentObjects;
	int			fc_numDependentObjects;
	int			fc_maxDependentObjects;
	ObjectAddressStack fc_mystack;
	ObjectAddressExtra fc_extra;

	/*
	 * 如果目标对象已经在外部递归级别中被访问，
	 * 则只需将当前 objflags 返回到该级别并退出。
	 * 这需要避免在循环依赖的情况下出现无限递归。
	 *
	 * 仅进行堆栈检查将导致依赖循环在任意点被打断，
	 * 即，循环中的第一个成员对象被访问的是最后一个被删除的对象。
	 * 这显然是不可行的。
	 * 不过，下面的内部依赖检查保证了我们不会在内部依赖处打断循环：
	 * 如果我们进入循环时是一个“拥有”的对象，
	 * 我们将切换并从“拥有”对象开始。
	 * 如果需要，我们可能可以搞点东西以避免在自动依赖处打断。
	 * 但目前没有已知的情况下会有这种必要。
	 */
	if (fc_stack_address_present_add_flags(fc_object, fc_objflags, fc_stack))
		return;

	/*
	 * 由于该函数递归，它可能会因深度依赖树而导致堆栈溢出，
	 * 而不仅仅是由于依赖循环。
	 */
	check_stack_depth();

	/*
	 * 目标对象也有可能已经被完全处理并放入 targetObjects。
	 * 如果是这样，再次只需将指定的 objflags 添加到其条目中并返回。
	 *
	 * （注意：在这些提前退出的情况下，我们可以释放调用者的
	 * 锁，因为对象现在被多次锁定；但看来不值得这样做。）
	 */
	if (fc_object_address_present_add_flags(fc_object, fc_objflags, fc_targetObjects))
		return;

	/*
	 * 如果目标对象被固定，则可以立即返回错误；
	 * 它不会记录任何依赖于它的对象。
	 */
	if (IsPinnedObject(fc_object->classId, fc_object->objectId))
		ereport(ERROR,
				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
				 errmsg("cannot drop %s because it is required by the database system",
						getObjectDescription(fc_object, false))));

	/*
	 * 目标对象可能在某些其他对象（其“所有者”）上存在内部依赖关系，
	 * 以及/或是扩展的成员（也被视为其所有者）。
	 * 如果是这样，并且如果我们不是从拥有对象递归而来，
	 * 则必须将此删除请求转换为对拥有对象的删除请求。
	 * （我们最终会再次递归到该对象，但必须先访问拥有对象，
	 * 以确保它在之后被删除。）了解这一点的方法是扫描
	 * pg_depend 条目，查看该对象所依赖的内容。
	 */
	ScanKeyInit(&fc_key[0],
				Anum_pg_depend_classid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_object->classId));
	ScanKeyInit(&fc_key[1],
				Anum_pg_depend_objid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_object->objectId));
	if (fc_object->objectSubId != 0)
	{
		/* 仅考虑此子对象的依赖关系 */
		ScanKeyInit(&fc_key[2],
					Anum_pg_depend_objsubid,
					BTEqualStrategyNumber, F_INT4EQ,
					Int32GetDatum(fc_object->objectSubId));
		fc_nkeys = 3;
	}
	else
	{
		/* 考虑该对象及其任何子对象的依赖关系 */
		fc_nkeys = 2;
	}

	fc_scan = systable_beginscan(*fc_depRel, DependDependerIndexId, true,
							  NULL, fc_nkeys, fc_key);

	/* 初始化循环可能填充的变量 */
	memset(&fc_owningObject, 0, sizeof(fc_owningObject));
	memset(&fc_partitionObject, 0, sizeof(fc_partitionObject));

	while (HeapTupleIsValid(fc_tup = systable_getnext(fc_scan)))
	{
		Form_pg_depend fc_foundDep = (Form_pg_depend) GETSTRUCT(fc_tup);

		fc_otherObject.classId = fc_foundDep->refclassid;
		fc_otherObject.objectId = fc_foundDep->refobjid;
		fc_otherObject.objectSubId = fc_foundDep->refobjsubid;

		/*
		 * 在扫描整个对象的依赖关系时，我们可能会发现行
		 * 将对象的子对象链接到对象本身。 （通常，
		 * 这种依赖关系是隐含的，但在涉及分区的某些情况下
		 * 我们必须使其显式。） 我们必须忽略这样的行以避免
		 * 无限递归。
		 */
		if (fc_otherObject.classId == fc_object->classId &&
			fc_otherObject.objectId == fc_object->objectId &&
			fc_object->objectSubId == 0)
			continue;

		switch (fc_foundDep->deptype)
		{
			case DEPENDENCY_NORMAL:
			case DEPENDENCY_AUTO:
			case DEPENDENCY_AUTO_EXTENSION:
				/* 没问题 */
				break;

			case DEPENDENCY_EXTENSION:

				/*
				 * 如果被告知，完全忽略 EXTENSION 依赖关系。 这个
				 * 标志通常用于防止在临时对象清理期间删除扩展，
				 * 即使在扩展脚本中创建了临时对象。
				 */
				if (fc_flags & PERFORM_DELETION_SKIP_EXTENSIONS)
					break;

				/*
				 * 如果其他对象是当前正在创建/更改的扩展，
				 * 忽略此依赖关系并继续删除。 这允许在
				 * 扩展的脚本中删除扩展的
				 * 对象，以及在这种脚本中创建的临时对象等特殊情况。
				 */
				if (creating_extension &&
					fc_otherObject.classId == ExtensionRelationId &&
					fc_otherObject.objectId == CurrentExtensionObject)
					break;

				/* 否则，视作内部依赖关系 */
				/* FALL THRU */

			case DEPENDENCY_INTERNAL:

				/*
				 * 此对象是另一个对象内部实现的一部分，
				 * 或是作为其它对象的扩展的一部分。 我们有三种情况：
				 *
				 * 1. 在最外层的递归级别时，我们必须不允许
				 * 删除。 但是，如果拥有对象在
				 * pendingObjects 中列出，只需释放调用者的锁并返回；
				 * 当我们到达 pending 列表中的该条目时，我们最终将
				 * 完成删除。
				 *
				 * 注意：上述陈述仅在此 pg_depend
				 * 条目在那时仍然存在的情况下成立；因此原则上，
				 * 我们可能会错过删除用户告诉我们删除的项目。
				 * 但是，不会产生不一致性：因为我们处于外层
				 * 级别，因此没有对象依赖于这个对象。
				 */
				if (fc_stack == NULL)
				{
					if (fc_pendingObjects &&
						object_address_present(&fc_otherObject, fc_pendingObjects))
					{
						systable_endscan(fc_scan);
						/* 需要释放调用者的锁；见下文注释 */
						ReleaseDeletionLock(fc_object);
						return;
					}

					/*
					 * 我们将实际发出错误消息的时间推迟到
					 * 此循环之后，以便使行为
					 * 独立于 pg_depend 条目的顺序，
					 * 至少如果没有多个 INTERNAL 和一个
					 * EXTENSION 依赖关系。 （如果有更多的，我们会抱怨
					 * 其中一个随机的。） 更倾向于抱怨
					 * EXTENSION，因为这通常是一个更重要的
					 * 依赖关系。
					 */
					if (!OidIsValid(fc_owningObject.classId) ||
						fc_foundDep->deptype == DEPENDENCY_EXTENSION)
						fc_owningObject = fc_otherObject;
					break;
				}

				/*
				 * 2. 当从此依赖关系的另一端递归时，
				 * 继续删除是可以的。 当
				 * 从涉及名义上的其他端作为组件的整个对象递归时，这种情况也是成立的。
				 * 由于可以有多个“拥有”对象，
				 * 我们必须允许与栈中多个级别向下的匹配。
				 */
				if (fc_stack_address_present_add_flags(&fc_otherObject, 0, fc_stack))
					break;

				/*
				 * 3. 并非所有的拥有对象都已被访问，
				 * 因此将此删除请求转换为删除此
				 * 拥有对象。
				 *
				 * 首先，释放调用者对该对象的锁，并获得
				 * 拥有对象的删除锁。 （我们必须释放
				 * 调用者的锁以避免与同时删除
				 * 拥有对象的死锁。）
				 */
				ReleaseDeletionLock(fc_object);
				AcquireDeletionLock(&fc_otherObject, 0);

				/*
				 * 拥有对象可能在我们等待锁定它时已被删除；
				 * 如果是这样，既没有它也没有当前对象再
				 * 是有趣的。 我们通过检查
				 * pg_depend 条目来测试这一点（请参见下方注释）。
				 */
				if (!systable_recheck_tuple(fc_scan, fc_tup))
				{
					systable_endscan(fc_scan);
					ReleaseDeletionLock(&fc_otherObject);
					return;
				}

				/*
				 * 无论如何，我们已经完成了扫描；最好在
				 * 递归之前关闭它，以减少峰值
				 * 资源消耗。
				 */
				systable_endscan(fc_scan);

				/*
				 * 好吧，递归到拥有对象，而不是继续进行。
				 *
				 * 我们不需要堆栈当前对象；我们希望遍历顺序
				 * 看起来就像原始引用已经
				 * 链接到拥有对象而不是这个对象一样。
				 *
				 * 依赖关系类型是“反向”依赖关系：如果要删除这个，
				 * 我们需要删除拥有对象，但这种链接永远不会
				 * 作为自动删除的原因。
				 */
				fc_findDependentObjects(&fc_otherObject,
									 DEPFLAG_REVERSE,
									 fc_flags,
									 fc_stack,
									 fc_targetObjects,
									 fc_pendingObjects,
									 fc_depRel);

				
/*
				 * 当前目标对象应该已经在处理拥有对象时添加到
				 * targetObjects；但是它可能只获得了与我们正在查看的
				 * 依赖相关的标志位。我们需要添加传递到此递归级别的
				 * objflags，否则我们可能会在 reportDependentObjects 中
				 * 收到虚假的失败（例如，如果我们是由于分区依赖而被调用）。
				 *
				 * 如果当前对象以某种方式没有被安排删除，发出警告。
				 * （这将意味着某人在递归到此之前删除了该依赖记录。）
				 * 另一种想法是重新获取当前对象的锁并继续尝试删除它，
				 * 但处理这种固有的竞争条件似乎不值得。
				 */
				if (!fc_object_address_present_add_flags(fc_object, fc_objflags,
													  fc_targetObjects))
					elog(ERROR, "deletion of owning object %s failed to delete %s",
						 getObjectDescription(&fc_otherObject, false),
						 getObjectDescription(fc_object, false));

				/* 完成。 */
				return;

			case DEPENDENCY_PARTITION_PRI:

				/*
				 * 请记住，此对象具有分区类型的依赖关系。
				 * 在依赖扫描后，如果我们没有找到
				 * 删除其分区依赖关系的理由，我们将抱怨。
				 */
				fc_objflags |= DEPFLAG_IS_PART;

				/*
				 * 还要记住主要的分区拥有者，以便用于错误
				 * 消息。如果有多个主要拥有者（应该没有），
				 * 我们将报告其中一个随机的。
				 */
				fc_partitionObject = fc_otherObject;
				break;

			case DEPENDENCY_PARTITION_SEC:

				/*
				 * 仅在错误消息中使用次要分区拥有者，
				 * 如果我们没有找到主要拥有者（这可能不应该发生）。
				 */
				if (!(fc_objflags & DEPFLAG_IS_PART))
					fc_partitionObject = fc_otherObject;

				/*
				 * 请记住，此对象具有分区类型的依赖关系。
				 * 在依赖扫描后，如果我们没有找到
				 * 删除其分区依赖关系的理由，我们将抱怨。
				 */
				fc_objflags |= DEPFLAG_IS_PART;
				break;

			default:
				elog(ERROR, "unrecognized dependency type '%c' for %s",
					 fc_foundDep->deptype, getObjectDescription(fc_object, false));
				break;
		}
	}

	systable_endscan(fc_scan);

	/*
	 * 如果我们在外层找到一个 INTERNAL 或 EXTENSION 依赖关系，
	 * 现在就抱怨。如果我们还找到了 PARTITION 依赖关系，
	 * 我们更倾向于报告 PARTITION 依赖关系。这是任意的，但
	 * 在实践中似乎更有用。
	 */
	if (OidIsValid(fc_owningObject.classId))
	{
		char	   *fc_otherObjDesc;

		if (OidIsValid(fc_partitionObject.classId))
			fc_otherObjDesc = getObjectDescription(&fc_partitionObject, false);
		else
			fc_otherObjDesc = getObjectDescription(&fc_owningObject, false);

		ereport(ERROR,
				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
				 errmsg("cannot drop %s because %s requires it",
						getObjectDescription(fc_object, false), fc_otherObjDesc),
				 errhint("You can drop %s instead.", fc_otherObjDesc)));
	}

	/*
	 * 接下来，识别所有直接依赖于当前对象的对象。
	 * 为了确保可预测的删除顺序，我们在 dependentObjects 中
	 * 收集它们，并在实际递归之前对列表进行排序。
	 * （在任何情况下删除顺序都是有效的，但这样做确保了
	 * DROP CASCADE 命令的一致输出，这对回归测试是有帮助的。）
	 */
	fc_maxDependentObjects = 128;	/* 任意初始分配 */
	fc_dependentObjects = (ObjectAddressAndFlags *)
		palloc(fc_maxDependentObjects * sizeof(ObjectAddressAndFlags));
	fc_numDependentObjects = 0;

	ScanKeyInit(&fc_key[0],
				Anum_pg_depend_refclassid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_object->classId));
	ScanKeyInit(&fc_key[1],
				Anum_pg_depend_refobjid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_object->objectId));
	if (fc_object->objectSubId != 0)
	{
		ScanKeyInit(&fc_key[2],
					Anum_pg_depend_refobjsubid,
					BTEqualStrategyNumber, F_INT4EQ,
					Int32GetDatum(fc_object->objectSubId));
		fc_nkeys = 3;
	}
	else
		fc_nkeys = 2;

	fc_scan = systable_beginscan(*fc_depRel, DependReferenceIndexId, true,
							  NULL, fc_nkeys, fc_key);

	while (HeapTupleIsValid(fc_tup = systable_getnext(fc_scan)))
	{
		Form_pg_depend fc_foundDep = (Form_pg_depend) GETSTRUCT(fc_tup);
		int			fc_subflags;

		fc_otherObject.classId = fc_foundDep->classid;
		fc_otherObject.objectId = fc_foundDep->objid;
		fc_otherObject.objectSubId = fc_foundDep->objsubid;

		/*
		 * 如果我们发现的是什么当前对象的子对象，直接忽略
		 * 它。（通常，这种依赖是隐式的，但在某些涉及分区的情况下
		 * 我们必须使其显式。）
		 */
		if (fc_otherObject.classId == fc_object->classId &&
			fc_otherObject.objectId == fc_object->objectId &&
			fc_object->objectSubId == 0)
			continue;

		/*
		 * 在递归到其之前必须锁定依赖对象。
		 */
		AcquireDeletionLock(&fc_otherObject, 0);

		/*
		 * 在我们等待锁定它的过程中，依赖对象可能已被删除；
		 * 如果是这样，我们不需要再对它做任何事情。
		 * 我们可以通过查看我们正在查看的 pg_depend 元组是否仍然存在
		 * 来便宜且独立地测试这一点。（如果对象被删除，元组也会被删除。）
		 */
		if (!systable_recheck_tuple(fc_scan, fc_tup))
		{
			/* 释放现在无用的锁 */
			ReleaseDeletionLock(&fc_otherObject);
			/* 并继续扫描依赖关系 */
			continue;
		}

		/*
		 * 我们确实需要删除它，因此识别要传递下去的 objflags，
		 * 这取决于依赖类型。
		 */
		switch (fc_foundDep->deptype)
		{
			case DEPENDENCY_NORMAL:
				fc_subflags = DEPFLAG_NORMAL;
				break;
			case DEPENDENCY_AUTO:
			case DEPENDENCY_AUTO_EXTENSION:
				fc_subflags = DEPFLAG_AUTO;
				break;
			case DEPENDENCY_INTERNAL:
				fc_subflags = DEPFLAG_INTERNAL;
				break;
			case DEPENDENCY_PARTITION_PRI:
			case DEPENDENCY_PARTITION_SEC:
				fc_subflags = DEPFLAG_PARTITION;
				break;
			case DEPENDENCY_EXTENSION:
				fc_subflags = DEPFLAG_EXTENSION;
				break;
			default:
				elog(ERROR, "unrecognized dependency type '%c' for %s",
					 fc_foundDep->deptype, getObjectDescription(fc_object, false));
				fc_subflags = 0;	/* 保持编译器安静 */
				break;
		}

		/* 并将其添加到待处理对象列表中 */
		if (fc_numDependentObjects >= fc_maxDependentObjects)
		{
			/* 如有需要则扩大数组 */
			fc_maxDependentObjects *= 2;
			fc_dependentObjects = (ObjectAddressAndFlags *)
				repalloc(fc_dependentObjects,
						 fc_maxDependentObjects * sizeof(ObjectAddressAndFlags));
		}

		fc_dependentObjects[fc_numDependentObjects].obj = fc_otherObject;
		fc_dependentObjects[fc_numDependentObjects].subflags = fc_subflags;
		fc_numDependentObjects++;
	}

	systable_endscan(fc_scan);

	/*
	 * 现在我们可以将依赖对象排序为稳定的访问顺序。
	 * 在这里使用 object_address_comparator 是安全的，因为 obj 字段
	 * 在 ObjectAddressAndFlags 中是第一个。
	 */
	if (fc_numDependentObjects > 1)
		qsort((void *) fc_dependentObjects, fc_numDependentObjects,
			  sizeof(ObjectAddressAndFlags),
			  fc_object_address_comparator);

	/*
	 * 现在递归到依赖对象。我们必须先访问它们，因为
	 * 它们必须在当前对象之前被删除。
	 */
	fc_mystack.object = fc_object;	/* 设置一个新的堆栈级别 */
	fc_mystack.flags = fc_objflags;
	fc_mystack.next = fc_stack;

	for (int fc_i = 0; fc_i < fc_numDependentObjects; fc_i++)
	{
		ObjectAddressAndFlags *fc_depObj = fc_dependentObjects + fc_i;

		fc_findDependentObjects(&fc_depObj->obj,
							 fc_depObj->subflags,
							 fc_flags,
							 &fc_mystack,
							 fc_targetObjects,
							 fc_pendingObjects,
							 fc_depRel);
	}

	pfree(fc_dependentObjects);

	/*
	 * 最后，我们可以将目标对象添加到 targetObjects。务必小心
	 * 包括从内层递归级别传回给我们的任何标志。记录“被依赖者”，
	 * 如果有的话，作为最重要的分区所有者；否则记录我们递归来的对象，
	 * 如果有的话。（reportDependentObjects() 中的逻辑只需要其中一个对象。）
	 */
	fc_extra.flags = fc_mystack.flags;
	if (fc_extra.flags & DEPFLAG_IS_PART)
		fc_extra.dependee = fc_partitionObject;
	else if (fc_stack)
		fc_extra.dependee = *fc_stack->object;
	else
		memset(&fc_extra.dependee, 0, sizeof(fc_extra.dependee));
	add_exact_object_address_extra(fc_object, &fc_extra, fc_targetObjects);
}

/*
 * reportDependentObjects - 报告依赖关系，如果是 RESTRICT 则失败
 *
 * 告诉用户我们将要删除的依赖对象
 * （或者需要删除，但由于 RESTRICT 模式被阻止）；
 * 如果有任何这样的对象，并且不是 CASCADE 模式，则报错。
 *
 *	targetObjects: 被计划删除的对象列表
 *	behavior: RESTRICT 或 CASCADE
 *	flags: 删除操作的其他标志
 *	origObject: 删除的基础对象，如果不可用则为 NULL
 *		（后者情况发生在 DROP OWNED 中）
 */
static void fc_reportDependentObjects(const ObjectAddresses *fc_targetObjects,
					   DropBehavior fc_behavior,
					   int fc_flags,
					   const ObjectAddress *fc_origObject)
{
	int			fc_msglevel = (fc_flags & PERFORM_DELETION_QUIETLY) ? DEBUG2 : NOTICE;
	bool		fc_ok = true;
	StringInfoData fc_clientdetail;
	StringInfoData fc_logdetail;
	int			fc_numReportedClient = 0;
	int			fc_numNotReportedClient = 0;
	int			fc_i;

	/*
	 * 如果我们需要删除任何分区依赖对象，请确保
	 * 我们也删除了它们的至少一个分区依赖。这
	 * 可以通过检查我们在某个时刻是否通过 PARTITION
	 * 依赖到达它们来检测。
	 *
	 * 我们只报告第一个这样的对象，因为在大多数情况下，触发此投诉的唯一方法是
	 * 明确尝试删除一个分区的分区对象。
	 */
	for (fc_i = 0; fc_i < fc_targetObjects->numrefs; fc_i++)
	{
		const ObjectAddressExtra *fc_extra = &fc_targetObjects->extras[fc_i];

		if ((fc_extra->flags & DEPFLAG_IS_PART) &&
			!(fc_extra->flags & DEPFLAG_PARTITION))
		{
			const ObjectAddress *fc_object = &fc_targetObjects->refs[fc_i];
			char	   *fc_otherObjDesc = getObjectDescription(&fc_extra->dependee,
															false);

			ereport(ERROR,
					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
					 errmsg("cannot drop %s because %s requires it",
							getObjectDescription(fc_object, false), fc_otherObjDesc),
					 errhint("You can drop %s instead.", fc_otherObjDesc)));
		}
	}

	/*
	 * 如果不应引发错误，并且 msglevel 过低无法显示
	 * 在客户端或服务器日志中，则没有必要进行其余工作。
	 */
	if (fc_behavior == DROP_CASCADE &&
		!message_level_is_interesting(fc_msglevel))
		return;

	/*
	 * 我们将报告给客户端的依赖项数量限制为
	 * MAX_REPORTED_DEPS，因为客户端软件可能无法很好地处理
	 * 巨大的错误字符串。服务器日志总是获得完整报告。
	 */
#define MAX_REPORTED_DEPS 100

	initStringInfo(&fc_clientdetail);
	initStringInfo(&fc_logdetail);

	/*
	 * 我们反向处理列表（即，按依赖顺序而不是删除
	 * 顺序），因为这使得显示更易于理解。
	 */
	for (fc_i = fc_targetObjects->numrefs - 1; fc_i >= 0; fc_i--)
	{
		const ObjectAddress *fc_obj = &fc_targetObjects->refs[fc_i];
		const ObjectAddressExtra *fc_extra = &fc_targetObjects->extras[fc_i];
		char	   *fc_objDesc;

		/* 忽略原始删除目标 */
		if (fc_extra->flags & DEPFLAG_ORIGINAL)
			continue;

		/* 也忽略子对象；我们将在其他地方报告整个对象 */
		if (fc_extra->flags & DEPFLAG_SUBOBJECT)
			continue;

		fc_objDesc = getObjectDescription(fc_obj, false);

		/* 同时删除的对象不需要报告 */
		if (fc_objDesc == NULL)
			continue;

		/*
		 * 如果在递归搜索的任何阶段，我们通过
		 * AUTO、INTERNAL、PARTITION 或 EXTENSION 依赖到达对象，
		 * 那么即使在 RESTRICT 模式下删除它也是可以的。
		 */
		if (fc_extra->flags & (DEPFLAG_AUTO |
							DEPFLAG_INTERNAL |
							DEPFLAG_PARTITION |
							DEPFLAG_EXTENSION))
		{
			/*
			 * auto-cascade 在 DEBUG2 中报告，而不是 msglevel。
			 * 我们不试图将其与常规消息结合，因为当
			 * client_min_messages 和 log_min_messages 不同时，
			 * 结果会令人困惑。
			 */
			ereport(DEBUG2,
					(errmsg_internal("drop auto-cascades to %s",
									 fc_objDesc)));
		}
		else if (fc_behavior == DROP_RESTRICT)
		{
			char	   *fc_otherDesc = getObjectDescription(&fc_extra->dependee,
														 false);

			if (fc_otherDesc)
			{
				if (fc_numReportedClient < MAX_REPORTED_DEPS)
				{
					/* 用换行符分隔条目 */
					if (fc_clientdetail.len != 0)
						appendStringInfoChar(&fc_clientdetail, '\n');
					appendStringInfo(&fc_clientdetail, _("%s depends on %s"),
									 fc_objDesc, fc_otherDesc);
					fc_numReportedClient++;
				}
				else
					fc_numNotReportedClient++;
				/* 用换行符分隔条目 */
				if (fc_logdetail.len != 0)
					appendStringInfoChar(&fc_logdetail, '\n');
				appendStringInfo(&fc_logdetail, _("%s depends on %s"),
								 fc_objDesc, fc_otherDesc);
				pfree(fc_otherDesc);
			}
			else
				fc_numNotReportedClient++;
			fc_ok = false;
		}
		else
		{
			if (fc_numReportedClient < MAX_REPORTED_DEPS)
			{
				/* 用换行符分隔条目 */
				if (fc_clientdetail.len != 0)
					appendStringInfoChar(&fc_clientdetail, '\n');
				appendStringInfo(&fc_clientdetail, _("drop cascades to %s"),
								 fc_objDesc);
				fc_numReportedClient++;
			}
			else
				fc_numNotReportedClient++;
			/* 用换行符分隔条目 */
			if (fc_logdetail.len != 0)
				appendStringInfoChar(&fc_logdetail, '\n');
			appendStringInfo(&fc_logdetail, _("drop cascades to %s"),
							 fc_objDesc);
		}

		pfree(fc_objDesc);
	}

	if (fc_numNotReportedClient > 0)
		appendStringInfo(&fc_clientdetail, ngettext("\nand %d other object "
												 "(see server log for list)",
												 "\nand %d other objects "
												 "(see server log for list)",
												 fc_numNotReportedClient),
						 fc_numNotReportedClient);

	if (!fc_ok)
	{
		if (fc_origObject)
			ereport(ERROR,
					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
					 errmsg("cannot drop %s because other objects depend on it",
							getObjectDescription(fc_origObject, false)),
					 errdetail_internal("%s", fc_clientdetail.data),
					 errdetail_log("%s", fc_logdetail.data),
					 errhint("Use DROP ... CASCADE to drop the dependent objects too.")));
		else
			ereport(ERROR,
					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
					 errmsg("cannot drop desired object(s) because other objects depend on them"),
					 errdetail_internal("%s", fc_clientdetail.data),
					 errdetail_log("%s", fc_logdetail.data),
					 errhint("Use DROP ... CASCADE to drop the dependent objects too.")));
	}
	else if (fc_numReportedClient > 1)
	{
		ereport(fc_msglevel,
				(errmsg_plural("drop cascades to %d other object",
							   "drop cascades to %d other objects",
							   fc_numReportedClient + fc_numNotReportedClient,
							   fc_numReportedClient + fc_numNotReportedClient),
				 errdetail_internal("%s", fc_clientdetail.data),
				 errdetail_log("%s", fc_logdetail.data)));
	}
	else if (fc_numReportedClient == 1)
	{
		/* 我们就按原样使用单个项目 */
		ereport(fc_msglevel,
				(errmsg_internal("%s", fc_clientdetail.data)));
	}

	pfree(fc_clientdetail.data);
	pfree(fc_logdetail.data);
}

/*
 * 通过 OID 删除对象。适用于大多数目录，如果不需要特殊处理。
 */
static void fc_DropObjectById(const ObjectAddress *fc_object)
{
	int			fc_cacheId;
	Relation	fc_rel;
	HeapTuple	fc_tup;

	fc_cacheId = get_object_catcache_oid(fc_object->classId);

	fc_rel = table_open(fc_object->classId, RowExclusiveLock);

	/*
	 * 如果存在，则使用 oid 列的系统缓存。
	 */
	if (fc_cacheId >= 0)
	{
		fc_tup = SearchSysCache1(fc_cacheId, ObjectIdGetDatum(fc_object->objectId));
		if (!HeapTupleIsValid(fc_tup))
			elog(ERROR, "cache lookup failed for %s %u",
				 get_object_class_descr(fc_object->classId), fc_object->objectId);

		CatalogTupleDelete(fc_rel, &fc_tup->t_self);

		ReleaseSysCache(fc_tup);
	}
	else
	{
		ScanKeyData fc_skey[1];
		SysScanDesc fc_scan;

		ScanKeyInit(&fc_skey[0],
					get_object_attnum_oid(fc_object->classId),
					BTEqualStrategyNumber, F_OIDEQ,
					ObjectIdGetDatum(fc_object->objectId));

		fc_scan = systable_beginscan(fc_rel, get_object_oid_index(fc_object->classId), true,
								  NULL, 1, fc_skey);

		/* 我们期望恰好一个匹配 */
		fc_tup = systable_getnext(fc_scan);
		if (!HeapTupleIsValid(fc_tup))
			elog(ERROR, "could not find tuple for %s %u",
				 get_object_class_descr(fc_object->classId), fc_object->objectId);

		CatalogTupleDelete(fc_rel, &fc_tup->t_self);

		systable_endscan(fc_scan);
	}

	table_close(fc_rel, RowExclusiveLock);
}

/*
 * deleteOneObject: 删除一个对象以执行 performDeletion。
 *
 * *depRel 是已经打开的 pg_depend 关系。
 */
static void fc_deleteOneObject(const ObjectAddress *fc_object, Relation *fc_depRel, int fc_flags)
{
	ScanKeyData fc_key[3];
	int			fc_nkeys;
	SysScanDesc fc_scan;
	HeapTuple	fc_tup;

	/* 删除对象的 DROP 钩子 */
	InvokeObjectDropHookArg(fc_object->classId, fc_object->objectId,
							fc_object->objectSubId, fc_flags);

	/*
	 * 如果我们正在并发地进行删除，请关闭 depRel。
	 * 对象删除子例程将提交当前事务，因此我们不能在 doDeletion()
	 * 之间保持关系打开。
	 */
	if (fc_flags & PERFORM_DELETION_CONCURRENTLY)
		table_close(*fc_depRel, RowExclusiveLock);

	/*
	 * 以对象类型相关的方式删除对象本身。
	 *
	 * 我们过去是在移除传出依赖链接后执行此操作的，但现在在此之前
	 * 执行也似乎合理。在并发情况下，我们 *必须* 按此顺序进行，
	 * 因为在调用 doDeletion() 之前无法进行任何事务性更新 ---
	 * 它们会立即被提交，如果删除随后失败，这就不合适了。
	 */
	fc_doDeletion(fc_object, fc_flags);

	/*
	 * 如果我们在上面关闭了 depRel，则重新打开它
	 */
	if (fc_flags & PERFORM_DELETION_CONCURRENTLY)
		*fc_depRel = table_open(DependRelationId, RowExclusiveLock);

	/*
	 * 现在移除任何链接到其他对象的 pg_depend 记录。
	 * （任何链接到此对象的记录应该已经被删除。）
	 *
	 * 当删除整个对象时（subId = 0），也要删除其子对象的所有 pg_depend 记录。
	 */
	ScanKeyInit(&fc_key[0],
				Anum_pg_depend_classid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_object->classId));
	ScanKeyInit(&fc_key[1],
				Anum_pg_depend_objid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_object->objectId));
	if (fc_object->objectSubId != 0)
	{
		ScanKeyInit(&fc_key[2],
					Anum_pg_depend_objsubid,
					BTEqualStrategyNumber, F_INT4EQ,
					Int32GetDatum(fc_object->objectSubId));
		fc_nkeys = 3;
	}
	else
		fc_nkeys = 2;

	fc_scan = systable_beginscan(*fc_depRel, DependDependerIndexId, true,
							  NULL, fc_nkeys, fc_key);

	while (HeapTupleIsValid(fc_tup = systable_getnext(fc_scan)))
	{
		CatalogTupleDelete(*fc_depRel, &fc_tup->t_self);
	}

	systable_endscan(fc_scan);

	/*
	 * 删除与该对象相关的共享依赖引用。如果
	 * subId = 0，也要删除子对象的记录。
	 */
	deleteSharedDependencyRecordsFor(fc_object->classId, fc_object->objectId,
									 fc_object->objectSubId);


	/*
	 * 删除与该对象相关的任何注释、安全标签或初始权限。
	 * （这是处理这些事情的方便之处，而不是让每种对象类型都知道这样做。）如上所述，
	 * 如果 subid 为零，所有这些函数也必须删除子对象的记录。
	 */
	DeleteComments(fc_object->objectId, fc_object->classId, fc_object->objectSubId);
	DeleteSecurityLabel(fc_object);
	fc_DeleteInitPrivs(fc_object);

	
/*
	 * CommandCounterIncrement在这里确保之前的所有更改对下一步删除都是可见的。
	 */
	CommandCounterIncrement();

	/*
	 * 我们完成了！
	 */
}

/*
 * doDeletion: 实际删除一个对象
 */
static void fc_doDeletion(const ObjectAddress *fc_object, int fc_flags)
{
	switch (getObjectClass(fc_object))
	{
		case OCLASS_CLASS:
			{
				char		fc_relKind = get_rel_relkind(fc_object->objectId);

				if (fc_relKind == RELKIND_INDEX ||
					fc_relKind == RELKIND_PARTITIONED_INDEX)
				{
					bool		fc_concurrent = ((fc_flags & PERFORM_DELETION_CONCURRENTLY) != 0);
					bool		fc_concurrent_lock_mode = ((fc_flags & PERFORM_DELETION_CONCURRENT_LOCK) != 0);

					Assert(fc_object->objectSubId == 0);
					index_drop(fc_object->objectId, fc_concurrent, fc_concurrent_lock_mode);
				}
				else
				{
					if (fc_object->objectSubId != 0)
						RemoveAttributeById(fc_object->objectId,
											fc_object->objectSubId);
					else
						heap_drop_with_catalog(fc_object->objectId);
				}

				/*
				 * 对于一个序列，除了移除堆外，还
				 * 删除 pg_sequence 元组
				 */
				if (fc_relKind == RELKIND_SEQUENCE)
					DeleteSequenceTuple(fc_object->objectId);
				break;
			}

		case OCLASS_PROC:
			RemoveFunctionById(fc_object->objectId);
			break;

		case OCLASS_TYPE:
			RemoveTypeById(fc_object->objectId);
			break;

		case OCLASS_CONSTRAINT:
			RemoveConstraintById(fc_object->objectId);
			break;

		case OCLASS_DEFAULT:
			RemoveAttrDefaultById(fc_object->objectId);
			break;

		case OCLASS_LARGEOBJECT:
			LargeObjectDrop(fc_object->objectId);
			break;

		case OCLASS_OPERATOR:
			RemoveOperatorById(fc_object->objectId);
			break;

		case OCLASS_REWRITE:
			RemoveRewriteRuleById(fc_object->objectId);
			break;

		case OCLASS_TRIGGER:
			RemoveTriggerById(fc_object->objectId);
			break;

		case OCLASS_STATISTIC_EXT:
			RemoveStatisticsById(fc_object->objectId);
			break;

		case OCLASS_TSCONFIG:
			RemoveTSConfigurationById(fc_object->objectId);
			break;

		case OCLASS_EXTENSION:
			RemoveExtensionById(fc_object->objectId);
			break;

		case OCLASS_POLICY:
			RemovePolicyById(fc_object->objectId);
			break;

		case OCLASS_PUBLICATION_NAMESPACE:
			RemovePublicationSchemaById(fc_object->objectId);
			break;

		case OCLASS_PUBLICATION_REL:
			RemovePublicationRelById(fc_object->objectId);
			break;

		case OCLASS_PUBLICATION:
			RemovePublicationById(fc_object->objectId);
			break;

		case OCLASS_CAST:
		case OCLASS_COLLATION:
		case OCLASS_CONVERSION:
		case OCLASS_LANGUAGE:
		case OCLASS_OPCLASS:
		case OCLASS_OPFAMILY:
		case OCLASS_AM:
		case OCLASS_AMOP:
		case OCLASS_AMPROC:
		case OCLASS_SCHEMA:
		case OCLASS_TSPARSER:
		case OCLASS_TSDICT:
		case OCLASS_TSTEMPLATE:
		case OCLASS_FDW:
		case OCLASS_FOREIGN_SERVER:
		case OCLASS_USER_MAPPING:
		case OCLASS_DEFACL:
		case OCLASS_EVENT_TRIGGER:
		case OCLASS_TRANSFORM:
			fc_DropObjectById(fc_object);
			break;

			/*
			 * 这些全局对象类型在这里不支持。
			 */
		case OCLASS_ROLE:
		case OCLASS_DATABASE:
		case OCLASS_TBLSPACE:
		case OCLASS_SUBSCRIPTION:
		case OCLASS_PARAMETER_ACL:
			elog(ERROR, "global objects cannot be deleted by doDeletion");
			break;

			/*
			 * 这里故意没有默认 case；我们希望编译器
			 * 在上面未处理新 OCLASS 时发出警告。
			 */
	}
}

/*
 * AcquireDeletionLock - 为删除对象获取合适的锁
 *
 * 接受与 performDeletion 相同的标志（虽然目前只有
 * PERFORM_DELETION_CONCURRENTLY 起作用）。
 *
 * 我们对关系使用 LockRelation，对其他所有对象使用 LockDatabaseObject。
 * 目前不支持跨数据库共享对象，因为没有调用者关心，但可以修改为使用 LockSharedObject。
 */
void AcquireDeletionLock(const ObjectAddress *fc_object, int fc_flags)
{
	if (fc_object->classId == RelationRelationId)
	{
		/*
		 * 在 DROP INDEX CONCURRENTLY 中，此时仅对
		 * 索引获得 ShareUpdateExclusiveLock。 index_drop() 
		 * 会在安全时提升锁。在所有其他情况下，我们需要完全独占
		 * 锁。
		 */
		if (fc_flags & PERFORM_DELETION_CONCURRENTLY)
			LockRelationOid(fc_object->objectId, ShareUpdateExclusiveLock);
		else
			LockRelationOid(fc_object->objectId, AccessExclusiveLock);
	}
	else
	{
		/* 假设我们应该锁定整个对象而不是子对象 */
		LockDatabaseObject(fc_object->classId, fc_object->objectId, 0,
						   AccessExclusiveLock);
	}
}

/*
 * ReleaseDeletionLock - 释放对象删除锁
 *
 * AcquireDeletionLock 的配对函数。
 */
void ReleaseDeletionLock(const ObjectAddress *fc_object)
{
	if (fc_object->classId == RelationRelationId)
		UnlockRelationOid(fc_object->objectId, AccessExclusiveLock);
	else
		/* 假设我们应该锁定整个对象而不是子对象 */
		UnlockDatabaseObject(fc_object->classId, fc_object->objectId, 0,
							 AccessExclusiveLock);
}

/*
 * recordDependencyOnExpr - 查找表达式依赖关系
 *
 * 用于查找规则、约束表达式等的依赖关系。
 *
 * 给定一个以节点树形式表示的表达式或查询，查找所有它引用的对象
 * （表、列、操作符、函数等）。记录从给定依赖对象到表达式中提及的
 * 每个对象的指定类型的依赖关系。
 *
 * rtable 是用于解释 varlevelsup=0 的变量的 rangetable。
 * 如果不期望有这样的变量，可以为 NIL。
 */
void recordDependencyOnExpr(const ObjectAddress *fc_depender,
					   Node *fc_expr, List *fc_rtable,
					   DependencyType fc_behavior)
{
	find_expr_references_context fc_context;

	fc_context.addrs = new_object_addresses();

	/* 为 varlevelsup = 0 的变量设置解释 */
	fc_context.rtables = list_make1(fc_rtable);

	/* 扫描表达式树以查找可引用的对象 */
	fc_find_expr_references_walker(fc_expr, &fc_context);

	/* 移除任何重复项 */
	fc_eliminate_duplicate_dependencies(fc_context.addrs);

	/* 并记录它们 */
	recordMultipleDependencies(fc_depender,
							   fc_context.addrs->refs, fc_context.addrs->numrefs,
							   fc_behavior);

	free_object_addresses(fc_context.addrs);
}

/*
 * recordDependencyOnSingleRelExpr - 查找表达式依赖关系
 *
 * 与上面相同，但仅期望引用一个关系（varno = 1 且 varlevelsup = 0）。传递
 * 关系 OID 而不是 ranged 表。一个额外的规则是，与该关系的组件列的
 * 依赖关系将标记为 'self_behavior'，而 'behavior' 用于其他所有情况；
 * 此外，如果 'reverse_self' 为真，则这些依赖关系会被反转，使得列依赖
 * 于表而不是反之。
 *
 * 注意：调用者应确保如果需要，可以单独创建对指定关系的整个表依赖关系。
 * 特别是，整个行 Var "relation.*" 不会导致此例程发出任何依赖项。
 * 这样的行为适合于普通查询的子表达式，因此其他情况需要必要时进行处理。
 */
void recordDependencyOnSingleRelExpr(const ObjectAddress *fc_depender,
								Node *fc_expr, Oid fc_relId,
								DependencyType fc_behavior,
								DependencyType fc_self_behavior,
								bool fc_reverse_self)
{
	find_expr_references_context fc_context;
	RangeTblEntry fc_rte;

	fc_context.addrs = new_object_addresses();

	/* 为处理 Vars 假装建立一个相当虚假的 rangetable 列表 */
	MemSet(&fc_rte, 0, sizeof(fc_rte));
	fc_rte.type = T_RangeTblEntry;
	fc_rte.rtekind = RTE_RELATION;
	fc_rte.relid = fc_relId;
	fc_rte.relkind = RELKIND_RELATION; /* 此处无需精确 */
	fc_rte.rellockmode = AccessShareLock;

	fc_context.rtables = list_make1(list_make1(&fc_rte));

	/* 扫描表达式树以查找可引用的对象 */
	fc_find_expr_references_walker(fc_expr, &fc_context);

	/* 移除任何重复项 */
	fc_eliminate_duplicate_dependencies(fc_context.addrs);

	/* 如有必要，分开自依赖关系 */
	if ((fc_behavior != fc_self_behavior || fc_reverse_self) &&
		fc_context.addrs->numrefs > 0)
	{
		ObjectAddresses *fc_self_addrs;
		ObjectAddress *fc_outobj;
		int			fc_oldref,
					fc_outrefs;

		fc_self_addrs = new_object_addresses();

		fc_outobj = fc_context.addrs->refs;
		fc_outrefs = 0;
		for (fc_oldref = 0; fc_oldref < fc_context.addrs->numrefs; fc_oldref++)
		{
			ObjectAddress *fc_thisobj = fc_context.addrs->refs + fc_oldref;

			if (fc_thisobj->classId == RelationRelationId &&
				fc_thisobj->objectId == fc_relId)
			{
				/* 将此引用移到 self_addrs 中 */
				add_exact_object_address(fc_thisobj, fc_self_addrs);
			}
			else
			{
				/* 保持在 context.addrs 中 */
				*fc_outobj = *fc_thisobj;
				fc_outobj++;
				fc_outrefs++;
			}
		}
		fc_context.addrs->numrefs = fc_outrefs;

		/* 以适当的方向记录自依赖关系 */
		if (!fc_reverse_self)
			recordMultipleDependencies(fc_depender,
									   fc_self_addrs->refs, fc_self_addrs->numrefs,
									   fc_self_behavior);
		else
		{
			/* 不能使用 recordMultipleDependencies，因此采用繁琐的方式 */
			int			fc_selfref;

			for (fc_selfref = 0; fc_selfref < fc_self_addrs->numrefs; fc_selfref++)
			{
				ObjectAddress *fc_thisobj = fc_self_addrs->refs + fc_selfref;

				recordDependencyOn(fc_thisobj, fc_depender, fc_self_behavior);
			}
		}

		free_object_addresses(fc_self_addrs);
	}

	/* 记录外部依赖关系 */
	recordMultipleDependencies(fc_depender,
							   fc_context.addrs->refs, fc_context.addrs->numrefs,
							   fc_behavior);

	free_object_addresses(fc_context.addrs);
}

/*
 * 递归搜索表达式树以查找对象引用。
 *
 * 注意：在许多情况下，我们不需要为表达式中涉及的数据类型创建依赖关系，
 * 因为我们将通过其他某个对象获得间接依赖。例如 Var 节点依赖于一个列，
 * 而该列又依赖于数据类型，OpExpr 节点依赖于操作符，而操作符又依赖于数据类型。
 * 然而，如果没有这样的间接依赖，例如在 Const 和 CoerceToDomain 节点中，
 * 我们确实需要一个类型依赖关系。
 *
 * 同样，我们不需要创建对排序规则的依赖关系，除非排序规则被新引入到表达式中。
 */
static bool fc_find_expr_references_walker(Node *fc_node,
							find_expr_references_context *fc_context)
{
	if (fc_node == NULL)
		return false;
	if (IsA(fc_node, Var))
	{
		Var		   *fc_var = (Var *) fc_node;
		List	   *fc_rtable;
		RangeTblEntry *fc_rte;

		/* 查找匹配的 rtable 条目，如果未找到则抱怨 */
		if (fc_var->varlevelsup >= list_length(fc_context->rtables))
			elog(ERROR, "invalid varlevelsup %d", fc_var->varlevelsup);
		fc_rtable = (List *) list_nth(fc_context->rtables, fc_var->varlevelsup);
		if (fc_var->varno <= 0 || fc_var->varno > list_length(fc_rtable))
			elog(ERROR, "invalid varno %d", fc_var->varno);
		fc_rte = rt_fetch(fc_var->varno, fc_rtable);

		/*
		 * 整行 Var 不引用任何特定列，因此不增加新的
		 * 依赖关系。（我们假设每个基础 rangetable 项生成一个整体表依赖关系。
		 * 虽然在找到直接引用关系的整行 Var 时，我们可以记录这样一个依赖关系，
		 * 但不清楚如何将其扩展到 JOIN 的整行 Var，因此似乎更好将责任留给
		 * 范围表。注意，这对识别独立表达式的依赖关系存在一些风险：
		 * 可能需要单独创建整个表引用。）
		 */
		if (fc_var->varattno == InvalidAttrNumber)
			return false;
		if (fc_rte->rtekind == RTE_RELATION)
		{
			/* 如果是普通关系，引用此列 */
			add_object_address(OCLASS_CLASS, fc_rte->relid, fc_var->varattno,
							   fc_context->addrs);
		}
		else if (fc_rte->rtekind == RTE_FUNCTION)
		{
			/* 可能需要对复合类型的列添加依赖关系 */
			/* （以外部方式完成，因为它有点繁琐） */
			fc_process_function_rte_ref(fc_rte, fc_var->varattno, fc_context);
		}

		/*
		 * 引用其他 RTE 类型的变量不需要额外的工作。特别是，连接别名变量可以忽略，因为它必须引用合并的 USING 列。相关的连接输入列也将在连接条件中引用，而在别名表达式中涉及的任何类型强制函数将在扫描 RTE 本身时处理。
		 */
		return false;
	}
	else if (IsA(fc_node, Const))
	{
		Const	   *fc_con = (Const *) fc_node;
		Oid			fc_objoid;

		/* 常量必须依赖于常量的数据类型 */
		add_object_address(OCLASS_TYPE, fc_con->consttype, 0,
						   fc_context->addrs);

		/*
		 * 我们还必须依赖于常量的排序规则：如果 CollateExpr 被常量折叠为简单常量，它可能不同于数据类型的排序规则。但是在排序规则为 "default" 的最常见情况下，我们可以节省工作，因为我们知道这是固定的。
		 */
		if (OidIsValid(fc_con->constcollid) &&
			fc_con->constcollid != DEFAULT_COLLATION_OID)
			add_object_address(OCLASS_COLLATION, fc_con->constcollid, 0,
							   fc_context->addrs);

		/*
		 * 如果是指向现有对象的regclass或类似字面量，则添加对该对象的引用。
		 * （目前，只有regclass和regconfig情况有任何可能的使用，但我们最好
		 * 一致地处理所有OID别名数据类型。）
		 */
		if (!fc_con->constisnull)
		{
			switch (fc_con->consttype)
			{
				case REGPROCOID:
				case REGPROCEDUREOID:
					fc_objoid = DatumGetObjectId(fc_con->constvalue);
					if (SearchSysCacheExists1(PROCOID,
											  ObjectIdGetDatum(fc_objoid)))
						add_object_address(OCLASS_PROC, fc_objoid, 0,
										   fc_context->addrs);
					break;
				case REGOPEROID:
				case REGOPERATOROID:
					fc_objoid = DatumGetObjectId(fc_con->constvalue);
					if (SearchSysCacheExists1(OPEROID,
											  ObjectIdGetDatum(fc_objoid)))
						add_object_address(OCLASS_OPERATOR, fc_objoid, 0,
										   fc_context->addrs);
					break;
				case REGCLASSOID:
					fc_objoid = DatumGetObjectId(fc_con->constvalue);
					if (SearchSysCacheExists1(RELOID,
											  ObjectIdGetDatum(fc_objoid)))
						add_object_address(OCLASS_CLASS, fc_objoid, 0,
										   fc_context->addrs);
					break;
				case REGTYPEOID:
					fc_objoid = DatumGetObjectId(fc_con->constvalue);
					if (SearchSysCacheExists1(TYPEOID,
											  ObjectIdGetDatum(fc_objoid)))
						add_object_address(OCLASS_TYPE, fc_objoid, 0,
										   fc_context->addrs);
					break;
				case REGCOLLATIONOID:
					fc_objoid = DatumGetObjectId(fc_con->constvalue);
					if (SearchSysCacheExists1(COLLOID,
											  ObjectIdGetDatum(fc_objoid)))
						add_object_address(OCLASS_COLLATION, fc_objoid, 0,
										   fc_context->addrs);
					break;
				case REGCONFIGOID:
					fc_objoid = DatumGetObjectId(fc_con->constvalue);
					if (SearchSysCacheExists1(TSCONFIGOID,
											  ObjectIdGetDatum(fc_objoid)))
						add_object_address(OCLASS_TSCONFIG, fc_objoid, 0,
										   fc_context->addrs);
					break;
				case REGDICTIONARYOID:
					fc_objoid = DatumGetObjectId(fc_con->constvalue);
					if (SearchSysCacheExists1(TSDICTOID,
											  ObjectIdGetDatum(fc_objoid)))
						add_object_address(OCLASS_TSDICT, fc_objoid, 0,
										   fc_context->addrs);
					break;

				case REGNAMESPACEOID:
					fc_objoid = DatumGetObjectId(fc_con->constvalue);
					if (SearchSysCacheExists1(NAMESPACEOID,
											  ObjectIdGetDatum(fc_objoid)))
						add_object_address(OCLASS_SCHEMA, fc_objoid, 0,
										   fc_context->addrs);
					break;

					/*
					 * regrole的依赖项应该在所有数据库之间共享，因此
					 * 明确禁止依赖项的存在。
					 */
				case REGROLEOID:
					ereport(ERROR,
							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
							 errmsg("constant of the type %s cannot be used here",
									"regrole")));
					break;
			}
		}
		return false;
	}
	else if (IsA(fc_node, Param))
	{
		Param	   *fc_param = (Param *) fc_node;

		/* 参数必须依赖于参数的数据类型 */
		add_object_address(OCLASS_TYPE, fc_param->paramtype, 0,
						   fc_context->addrs);
		/* 和其排序规则，就像对于Consts一样 */
		if (OidIsValid(fc_param->paramcollid) &&
			fc_param->paramcollid != DEFAULT_COLLATION_OID)
			add_object_address(OCLASS_COLLATION, fc_param->paramcollid, 0,
							   fc_context->addrs);
	}
	else if (IsA(fc_node, FuncExpr))
	{
		FuncExpr   *fc_funcexpr = (FuncExpr *) fc_node;

		add_object_address(OCLASS_PROC, fc_funcexpr->funcid, 0,
						   fc_context->addrs);
		/* 继续查看参数 */
	}
	else if (IsA(fc_node, OpExpr))
	{
		OpExpr	   *fc_opexpr = (OpExpr *) fc_node;

		add_object_address(OCLASS_OPERATOR, fc_opexpr->opno, 0,
						   fc_context->addrs);
		/* 继续查看参数 */
	}
	else if (IsA(fc_node, DistinctExpr))
	{
		DistinctExpr *fc_distinctexpr = (DistinctExpr *) fc_node;

		add_object_address(OCLASS_OPERATOR, fc_distinctexpr->opno, 0,
						   fc_context->addrs);
		/* 继续查看参数 */
	}
	else if (IsA(fc_node, NullIfExpr))
	{
		NullIfExpr *fc_nullifexpr = (NullIfExpr *) fc_node;

		add_object_address(OCLASS_OPERATOR, fc_nullifexpr->opno, 0,
						   fc_context->addrs);
		/* 继续查看参数 */
	}
	else if (IsA(fc_node, ScalarArrayOpExpr))
	{
		ScalarArrayOpExpr *fc_opexpr = (ScalarArrayOpExpr *) fc_node;

		add_object_address(OCLASS_OPERATOR, fc_opexpr->opno, 0,
						   fc_context->addrs);
		/* 继续查看参数 */
	}
	else if (IsA(fc_node, Aggref))
	{
		Aggref	   *fc_aggref = (Aggref *) fc_node;

		add_object_address(OCLASS_PROC, fc_aggref->aggfnoid, 0,
						   fc_context->addrs);
		/* 继续查看参数 */
	}
	else if (IsA(fc_node, WindowFunc))
	{
		WindowFunc *fc_wfunc = (WindowFunc *) fc_node;

		add_object_address(OCLASS_PROC, fc_wfunc->winfnoid, 0,
						   fc_context->addrs);
		/* 继续查看参数 */
	}
	else if (IsA(fc_node, SubscriptingRef))
	{
		SubscriptingRef *fc_sbsref = (SubscriptingRef *) fc_node;

		/*
		 * refexpr应该提供足够的依赖于refcontainertype，
		 * 而该类型又依赖于refelemtype。然而，自定义
		 * 下标处理程序可能会将refrestype设置为
		 * 与这两者不同的东西，在这种情况下我们最好记录下来。
		 */
		if (fc_sbsref->refrestype != fc_sbsref->refcontainertype &&
			fc_sbsref->refrestype != fc_sbsref->refelemtype)
			add_object_address(OCLASS_TYPE, fc_sbsref->refrestype, 0,
							   fc_context->addrs);
		/* 继续查看参数 */
	}
	else if (IsA(fc_node, SubPlan))
	{
		/* 如果我们需要这个案例，可能需要额外的工作 */
		elog(ERROR, "already-planned subqueries not supported");
	}
	else if (IsA(fc_node, FieldSelect))
	{
		FieldSelect *fc_fselect = (FieldSelect *) fc_node;
		Oid			fc_argtype = getBaseType(exprType((Node *) fc_fselect->arg));
		Oid			fc_reltype = get_typ_typrelid(fc_argtype);

		/*
		 * 我们需要依赖于FieldSelect中指定的特定列，
		 * 假设我们能识别pg_class OID。（目前我们
		 * 可能总是可以，但将来可能会出现argtype为
		 * RECORDOID的情况。）如果我们能创建列依赖
		 * 那么我们就不需要依赖于列的类型；但如果不能，
		 * 则依赖于类型，因为它可能在表达式的其他地方没有出现。
		 */
		if (OidIsValid(fc_reltype))
			add_object_address(OCLASS_CLASS, fc_reltype, fc_fselect->fieldnum,
							   fc_context->addrs);
		else
			add_object_address(OCLASS_TYPE, fc_fselect->resulttype, 0,
							   fc_context->addrs);
		/* 排序规则在其他地方可能也不会被引用 */
		if (OidIsValid(fc_fselect->resultcollid) &&
			fc_fselect->resultcollid != DEFAULT_COLLATION_OID)
			add_object_address(OCLASS_COLLATION, fc_fselect->resultcollid, 0,
							   fc_context->addrs);
	}
	else if (IsA(fc_node, FieldStore))
	{
		FieldStore *fc_fstore = (FieldStore *) fc_node;
		Oid			fc_reltype = get_typ_typrelid(fc_fstore->resulttype);

		/* 与FieldSelect类似的考虑，但多个列 */
		if (OidIsValid(fc_reltype))
		{
			ListCell   *fc_l;

			foreach(fc_l, fc_fstore->fieldnums)
				add_object_address(OCLASS_CLASS, fc_reltype, lfirst_int(fc_l),
								   fc_context->addrs);
		}
		else
			add_object_address(OCLASS_TYPE, fc_fstore->resulttype, 0,
							   fc_context->addrs);
	}
	else if (IsA(fc_node, RelabelType))
	{
		RelabelType *fc_relab = (RelabelType *) fc_node;

		/* 由于没有函数依赖，需依赖于类型 */
		add_object_address(OCLASS_TYPE, fc_relab->resulttype, 0,
						   fc_context->addrs);
		/* 排序规则在其他地方可能也不会被引用 */
		if (OidIsValid(fc_relab->resultcollid) &&
			fc_relab->resultcollid != DEFAULT_COLLATION_OID)
			add_object_address(OCLASS_COLLATION, fc_relab->resultcollid, 0,
							   fc_context->addrs);
	}
	else if (IsA(fc_node, CoerceViaIO))
	{
		CoerceViaIO *fc_iocoerce = (CoerceViaIO *) fc_node;

		/* 由于没有暴露的函数，需依赖于类型 */
		add_object_address(OCLASS_TYPE, fc_iocoerce->resulttype, 0,
						   fc_context->addrs);
		/* 排序规则在其他地方可能也不会被引用 */
		if (OidIsValid(fc_iocoerce->resultcollid) &&
			fc_iocoerce->resultcollid != DEFAULT_COLLATION_OID)
			add_object_address(OCLASS_COLLATION, fc_iocoerce->resultcollid, 0,
							   fc_context->addrs);
	}
	else if (IsA(fc_node, ArrayCoerceExpr))
	{
		ArrayCoerceExpr *fc_acoerce = (ArrayCoerceExpr *) fc_node;

		/* 如上，依赖于类型 */
		add_object_address(OCLASS_TYPE, fc_acoerce->resulttype, 0,
						   fc_context->addrs);
		/* 排序规则在其他地方可能也不会被引用 */
		if (OidIsValid(fc_acoerce->resultcollid) &&
			fc_acoerce->resultcollid != DEFAULT_COLLATION_OID)
			add_object_address(OCLASS_COLLATION, fc_acoerce->resultcollid, 0,
							   fc_context->addrs);
		/* 继续查看参数 */
	}
	else if (IsA(fc_node, ConvertRowtypeExpr))
	{
		ConvertRowtypeExpr *fc_cvt = (ConvertRowtypeExpr *) fc_node;

		/* 由于没有函数依赖，需依赖于类型 */
		add_object_address(OCLASS_TYPE, fc_cvt->resulttype, 0,
						   fc_context->addrs);
	}
	else if (IsA(fc_node, CollateExpr))
	{
		CollateExpr *fc_coll = (CollateExpr *) fc_node;

		add_object_address(OCLASS_COLLATION, fc_coll->collOid, 0,
						   fc_context->addrs);
	}
	else if (IsA(fc_node, RowExpr))
	{
		RowExpr    *fc_rowexpr = (RowExpr *) fc_node;

		add_object_address(OCLASS_TYPE, fc_rowexpr->row_typeid, 0,
						   fc_context->addrs);
	}
	else if (IsA(fc_node, RowCompareExpr))
	{
		RowCompareExpr *fc_rcexpr = (RowCompareExpr *) fc_node;
		ListCell   *fc_l;

		foreach(fc_l, fc_rcexpr->opnos)
		{
			add_object_address(OCLASS_OPERATOR, lfirst_oid(fc_l), 0,
							   fc_context->addrs);
		}
		foreach(fc_l, fc_rcexpr->opfamilies)
		{
			add_object_address(OCLASS_OPFAMILY, lfirst_oid(fc_l), 0,
							   fc_context->addrs);
		}
		/* 继续查看参数 */
	}
	else if (IsA(fc_node, CoerceToDomain))
	{
		CoerceToDomain *fc_cd = (CoerceToDomain *) fc_node;

		add_object_address(OCLASS_TYPE, fc_cd->resulttype, 0,
						   fc_context->addrs);
	}
	else if (IsA(fc_node, NextValueExpr))
	{
		NextValueExpr *fc_nve = (NextValueExpr *) fc_node;

		add_object_address(OCLASS_CLASS, fc_nve->seqid, 0,
						   fc_context->addrs);
	}
	else if (IsA(fc_node, OnConflictExpr))
	{
		OnConflictExpr *fc_onconflict = (OnConflictExpr *) fc_node;

		if (OidIsValid(fc_onconflict->constraint))
			add_object_address(OCLASS_CONSTRAINT, fc_onconflict->constraint, 0,
							   fc_context->addrs);
		/* 继续查看参数 */
	}
	else if (IsA(fc_node, SortGroupClause))
	{
		SortGroupClause *fc_sgc = (SortGroupClause *) fc_node;

		add_object_address(OCLASS_OPERATOR, fc_sgc->eqop, 0,
						   fc_context->addrs);
		if (OidIsValid(fc_sgc->sortop))
			add_object_address(OCLASS_OPERATOR, fc_sgc->sortop, 0,
							   fc_context->addrs);
		return false;
	}
	else if (IsA(fc_node, WindowClause))
	{
		WindowClause *fc_wc = (WindowClause *) fc_node;

		if (OidIsValid(fc_wc->startInRangeFunc))
			add_object_address(OCLASS_PROC, fc_wc->startInRangeFunc, 0,
							   fc_context->addrs);
		if (OidIsValid(fc_wc->endInRangeFunc))
			add_object_address(OCLASS_PROC, fc_wc->endInRangeFunc, 0,
							   fc_context->addrs);
		if (OidIsValid(fc_wc->inRangeColl) &&
			fc_wc->inRangeColl != DEFAULT_COLLATION_OID)
			add_object_address(OCLASS_COLLATION, fc_wc->inRangeColl, 0,
							   fc_context->addrs);
		/* 继续查看子结构 */
	}
	else if (IsA(fc_node, CTECycleClause))
	{
		CTECycleClause *fc_cc = (CTECycleClause *) fc_node;

		if (OidIsValid(fc_cc->cycle_mark_type))
			add_object_address(OCLASS_TYPE, fc_cc->cycle_mark_type, 0,
							   fc_context->addrs);
		if (OidIsValid(fc_cc->cycle_mark_collation))
			add_object_address(OCLASS_COLLATION, fc_cc->cycle_mark_collation, 0,
							   fc_context->addrs);
		if (OidIsValid(fc_cc->cycle_mark_neop))
			add_object_address(OCLASS_OPERATOR, fc_cc->cycle_mark_neop, 0,
							   fc_context->addrs);
		/* 继续查看子结构 */
	}
	else if (IsA(fc_node, Query))
	{
		/* 递归进入RTE子查询或尚未规划的子链接子查询 */
		Query	   *fc_query = (Query *) fc_node;
		ListCell   *fc_lc;
		bool		fc_result;

		/*
		 * 为子查询的rtable中提到的每个普通关系添加整体关系引用，
		 * 并确保我们为连接别名列表中使用的任何类型强制转换
		 * 函数添加引用。
		 *
		 * 注意：query_tree_walker负责递归进入RTE_FUNCTION
		 * RTE、子查询等，因此这里无需处理。然而，我们必须
		 * 告诉它不要访问连接别名列表，否则我们将为连接
		 * 输入列添加引用，无论它们是否在我们的查询中实际使用。
		 *
		 * 注意：我们无需担心提到的排序规则
		 * RTE_VALUES或RTE_CTE RTE，因为这些必须只是复制
		 * 在查询的其他部分中引用的排序规则。我们确实需要
		 * 担心在RTE_FUNCTION中提到的排序规则，但在我们
		 * 递归到RangeTblFunction节点时会处理这些。
		 */
		foreach(fc_lc, fc_query->rtable)
		{
			RangeTblEntry *fc_rte = (RangeTblEntry *) lfirst(fc_lc);

			switch (fc_rte->rtekind)
			{
				case RTE_RELATION:
					add_object_address(OCLASS_CLASS, fc_rte->relid, 0,
									   fc_context->addrs);
					break;
				case RTE_JOIN:

					/*
					 * 仅检查合并JOIN USING列的joinaliasvars条目。
					 * 只有这些条目可能包含
					 * 类型强制转换函数。此外，它们的连接输入
					 * 列必须在连接条件中被引用，因此这
					 * 不会意外地添加对未使用连接
					 * 输入列的引用。（我们希望引用类型强制转换
					 * 函数，即使合并列在任何地方没有被显式使用，
					 * 以保护可能作为整行变量扩展的连接RTE，
					 * 并且因为允许丢弃在我们的查询树中存在的函数， 
					 * 无论它是否被调用，似乎都是个坏主意。）
					 */
					fc_context->rtables = lcons(fc_query->rtable, fc_context->rtables);
					for (int fc_i = 0; fc_i < fc_rte->joinmergedcols; fc_i++)
					{
						Node	   *fc_aliasvar = list_nth(fc_rte->joinaliasvars, fc_i);

						if (!IsA(fc_aliasvar, Var))
							fc_find_expr_references_walker(fc_aliasvar, fc_context);
					}
					fc_context->rtables = list_delete_first(fc_context->rtables);
					break;
				default:
					break;
			}
		}

		/*
		 * 如果查询是INSERT或UPDATE，我们应该为每个目标列创建依赖
		 * 以防止特定目标列被丢弃。尽管我们将在query_tree_walker中再次访问
		 * TargetEntry节点，但我们将没有足够的上下文来方便地完成此操作，
		 * 所以在此进行。
		 */
		if (fc_query->commandType == CMD_INSERT ||
			fc_query->commandType == CMD_UPDATE)
		{
			RangeTblEntry *fc_rte;

			if (fc_query->resultRelation <= 0 ||
				fc_query->resultRelation > list_length(fc_query->rtable))
				elog(ERROR, "invalid resultRelation %d",
					 fc_query->resultRelation);
			fc_rte = rt_fetch(fc_query->resultRelation, fc_query->rtable);
			if (fc_rte->rtekind == RTE_RELATION)
			{
				foreach(fc_lc, fc_query->targetList)
				{
					TargetEntry *fc_tle = (TargetEntry *) lfirst(fc_lc);

					if (fc_tle->resjunk)
						continue;	/* 忽略垃圾tlist项目 */
					add_object_address(OCLASS_CLASS, fc_rte->relid, fc_tle->resno,
									   fc_context->addrs);
				}
			}
		}

		/*
		 * 添加对查询的constraintDeps中列出的约束的依赖
		 */
		foreach(fc_lc, fc_query->constraintDeps)
		{
			add_object_address(OCLASS_CONSTRAINT, lfirst_oid(fc_lc), 0,
							   fc_context->addrs);
		}

		/* 检查查询的子结构 */
		fc_context->rtables = lcons(fc_query->rtable, fc_context->rtables);
		fc_result = query_tree_walker(fc_query,
								   fc_find_expr_references_walker,
								   (void *) fc_context,
								   QTW_IGNORE_JOINALIASES |
								   QTW_EXAMINE_SORTGROUP);
		fc_context->rtables = list_delete_first(fc_context->rtables);
		return fc_result;
	}
	else if (IsA(fc_node, SetOperationStmt))
	{
		SetOperationStmt *fc_setop = (SetOperationStmt *) fc_node;

		/* 我们需要查看groupClauses中的操作符引用 */
		fc_find_expr_references_walker((Node *) fc_setop->groupClauses, fc_context);
		/* 继续查看子节点 */
	}
	else if (IsA(fc_node, RangeTblFunction))
	{
		RangeTblFunction *fc_rtfunc = (RangeTblFunction *) fc_node;
		ListCell   *fc_ct;

		/*
		 * 为RECORD函数的列定义列表中使用的任何数据类型和排序规则添加引用。
		 * （对于其他情况，依赖于函数本身应该就足够了。）
		 */
		foreach(fc_ct, fc_rtfunc->funccoltypes)
		{
			add_object_address(OCLASS_TYPE, lfirst_oid(fc_ct), 0,
							   fc_context->addrs);
		}
		foreach(fc_ct, fc_rtfunc->funccolcollations)
		{
			Oid			fc_collid = lfirst_oid(fc_ct);

			if (OidIsValid(fc_collid) && fc_collid != DEFAULT_COLLATION_OID)
				add_object_address(OCLASS_COLLATION, fc_collid, 0,
								   fc_context->addrs);
		}
	}
	else if (IsA(fc_node, TableFunc))
	{
		TableFunc  *fc_tf = (TableFunc *) fc_node;
		ListCell   *fc_ct;

		/*
		 * 为TableFunc中使用的数据类型和排序规则添加引用。
		 */
		foreach(fc_ct, fc_tf->coltypes)
		{
			add_object_address(OCLASS_TYPE, lfirst_oid(fc_ct), 0,
							   fc_context->addrs);
		}
		foreach(fc_ct, fc_tf->colcollations)
		{
			Oid			fc_collid = lfirst_oid(fc_ct);

			if (OidIsValid(fc_collid) && fc_collid != DEFAULT_COLLATION_OID)
				add_object_address(OCLASS_COLLATION, fc_collid, 0,
								   fc_context->addrs);
		}
	}
	else if (IsA(fc_node, TableSampleClause))
	{
		TableSampleClause *fc_tsc = (TableSampleClause *) fc_node;

		add_object_address(OCLASS_PROC, fc_tsc->tsmhandler, 0,
						   fc_context->addrs);
		/* 继续查看参数 */
	}

	return expression_tree_walker(fc_node, fc_find_expr_references_walker,
								  (void *) fc_context);
}

/*
 * find_expr_references_walker子例程：处理对RTE_FUNCTION RTE的Var引用
 */
static void fc_process_function_rte_ref(RangeTblEntry *fc_rte, AttrNumber fc_attnum,
						 find_expr_references_context *fc_context)
{
	int			fc_atts_done = 0;
	ListCell   *fc_lc;

	/*
	 * 确定哪个RangeTblFunction生成此attnum，并检查它是否
	 * 返回复合类型。如果返回的话，我们最好对复合类型的
	 * 引用列（或实际上是其关联关系）建立依赖关系。
	 */
	foreach(fc_lc, fc_rte->functions)
	{
		RangeTblFunction *fc_rtfunc = (RangeTblFunction *) lfirst(fc_lc);

		if (fc_attnum > fc_atts_done &&
			fc_attnum <= fc_atts_done + fc_rtfunc->funccolcount)
		{
			TupleDesc	fc_tupdesc;

			/* 如果它有coldeflist，它肯定返回RECORD */
			if (fc_rtfunc->funccolnames != NIL)
				fc_tupdesc = NULL; /* 没必要费劲 */
			else
				fc_tupdesc = get_expr_result_tupdesc(fc_rtfunc->funcexpr, true);
			if (fc_tupdesc && fc_tupdesc->tdtypeid != RECORDOID)
			{
				/*
				 * 命名复合类型，因此单独的列可能会被
				 * 丢弃。对此特定列建立依赖关系。
				 */
				Oid			fc_reltype = get_typ_typrelid(fc_tupdesc->tdtypeid);

				Assert(fc_attnum - fc_atts_done <= fc_tupdesc->natts);
				if (OidIsValid(fc_reltype))	/* 这会失败吗? */
					add_object_address(OCLASS_CLASS, fc_reltype,
									   fc_attnum - fc_atts_done,
									   fc_context->addrs);
				return;
			}
			/* 没什么可做的；函数的结果类型在其他地方处理 */
			return;
		}
		fc_atts_done += fc_rtfunc->funccolcount;
	}

	/* 如果我们到这里，必须是在寻找序号列 */
	if (fc_rte->funcordinality && fc_attnum == fc_atts_done + 1)
		return;

	/* 这可能不会发生... */
	ereport(ERROR,
			(errcode(ERRCODE_UNDEFINED_COLUMN),
			 errmsg("column %d of relation \"%s\" does not exist",
					fc_attnum, fc_rte->eref->aliasname)));
}

/*
 * 给定一个依赖引用的数组，消除任何重复项。
 */
static void fc_eliminate_duplicate_dependencies(ObjectAddresses *fc_addrs)
{
	ObjectAddress *fc_priorobj;
	int			fc_oldref,
				fc_newrefs;

	/*
	 * 如果数组有“额外”数据，我们无法对其进行排序，因为没有办法
	 * 来保持同步。幸运的是，在这种特性组合下是不需要的。
	 */
	Assert(!fc_addrs->extras);

	if (fc_addrs->numrefs <= 1)
		return;					/* 无需操作 */

	/* 对引用进行排序，以便重复项相邻 */
	qsort((void *) fc_addrs->refs, fc_addrs->numrefs, sizeof(ObjectAddress),
		  fc_object_address_comparator);

	/* 移除重复项 */
	fc_priorobj = fc_addrs->refs;
	fc_newrefs = 1;
	for (fc_oldref = 1; fc_oldref < fc_addrs->numrefs; fc_oldref++)
	{
		ObjectAddress *fc_thisobj = fc_addrs->refs + fc_oldref;

		if (fc_priorobj->classId == fc_thisobj->classId &&
			fc_priorobj->objectId == fc_thisobj->objectId)
		{
			if (fc_priorobj->objectSubId == fc_thisobj->objectSubId)
				continue;		/* 相同，因此丢弃这个对象 */

			/*
			 * 如果我们有一个整个对象的引用和同一对象的部分
			 * 的引用，我们不需要整个对象的引用
			 * （例如，我们不需要同时引用表 foo 和
			 * 列 foo.bar）。整个对象引用总是在排序列表
			 * 中首先出现。
			 */
			if (fc_priorobj->objectSubId == 0)
			{
				/* 用部分引用替换整个引用 */
				fc_priorobj->objectSubId = fc_thisobj->objectSubId;
				continue;
			}
		}
		/* 不相同，因此将这个对象添加到输出集合 */
		fc_priorobj++;
		*fc_priorobj = *fc_thisobj;
		fc_newrefs++;
	}

	fc_addrs->numrefs = fc_newrefs;
}

/*
 * ObjectAddress 项的 qsort 比较器
 */
static int fc_object_address_comparator(const void *fc_a, const void *fc_b)
{
	const ObjectAddress *fc_obja = (const ObjectAddress *) fc_a;
	const ObjectAddress *fc_objb = (const ObjectAddress *) fc_b;

	/*
	 * 主要排序键是 OID 降序。大多数情况下，这将导致
	 * 更新的对象排在旧对象之前，这可能是删除的
	 * 正确顺序。
	 */
	if (fc_obja->objectId > fc_objb->objectId)
		return -1;
	if (fc_obja->objectId < fc_objb->objectId)
		return 1;

	/*
	 * 接下来按目录 ID 排序，以防相同的 OID 出现在不同
	 * 的目录中。这里的排序方向相当任意。
	 */
	if (fc_obja->classId < fc_objb->classId)
		return -1;
	if (fc_obja->classId > fc_objb->classId)
		return 1;

	/*
	 * 最后，按对象子 ID 排序。
	 *
	 * 我们将子 ID 作为无符号整数进行排序，以便 0（整个对象）将
	 * 排在第一。这对 eliminate_duplicate_dependencies 至关重要，
	 * 也是 findDependentObjects 的最佳排序。
	 */
	if ((unsigned int) fc_obja->objectSubId < (unsigned int) fc_objb->objectSubId)
		return -1;
	if ((unsigned int) fc_obja->objectSubId > (unsigned int) fc_objb->objectSubId)
		return 1;
	return 0;
}

/*
 * 处理可扩展的 ObjectAddress 项数组的例程。
 *
 * new_object_addresses: 创建一个新的 ObjectAddresses 数组。
 */
ObjectAddresses *
new_object_addresses(void)
{
	ObjectAddresses *fc_addrs;

	fc_addrs = palloc(sizeof(ObjectAddresses));

	fc_addrs->numrefs = 0;
	fc_addrs->maxrefs = 32;
	fc_addrs->refs = (ObjectAddress *)
		palloc(fc_addrs->maxrefs * sizeof(ObjectAddress));
	fc_addrs->extras = NULL;		/* 直到/除非需要 */

	return fc_addrs;
}

/*
 * 向 ObjectAddresses 数组添加条目。
 *
 * 使用 ObjectClass 指定类比直接使用目录 OID 更方便。
 */
static void add_object_address(ObjectClass fc_oclass, Oid fc_objectId, int32 fc_subId,
				   ObjectAddresses *fc_addrs)
{
	ObjectAddress *fc_item;

	/*
	 * 确保 object_classes 随着 ObjectClass 枚举保持更新。
	 */
	StaticAssertStmt(lengthof(object_classes) == LAST_OCLASS + 1,
					 "object_classes[] must cover all ObjectClasses");

	/* 如有需要则扩大数组 */
	if (fc_addrs->numrefs >= fc_addrs->maxrefs)
	{
		fc_addrs->maxrefs *= 2;
		fc_addrs->refs = (ObjectAddress *)
			repalloc(fc_addrs->refs, fc_addrs->maxrefs * sizeof(ObjectAddress));
		Assert(!fc_addrs->extras);
	}
	/* 记录此项 */
	fc_item = fc_addrs->refs + fc_addrs->numrefs;
	fc_item->classId = object_classes[fc_oclass];
	fc_item->objectId = fc_objectId;
	fc_item->objectSubId = fc_subId;
	fc_addrs->numrefs++;
}

/*
 * 向 ObjectAddresses 数组添加条目。
 *
 * 与上面相同，但精确指定条目。
 */
void add_exact_object_address(const ObjectAddress *fc_object,
						 ObjectAddresses *fc_addrs)
{
	ObjectAddress *fc_item;

	/* 如有需要则扩大数组 */
	if (fc_addrs->numrefs >= fc_addrs->maxrefs)
	{
		fc_addrs->maxrefs *= 2;
		fc_addrs->refs = (ObjectAddress *)
			repalloc(fc_addrs->refs, fc_addrs->maxrefs * sizeof(ObjectAddress));
		Assert(!fc_addrs->extras);
	}
	/* 记录此项 */
	fc_item = fc_addrs->refs + fc_addrs->numrefs;
	*fc_item = *fc_object;
	fc_addrs->numrefs++;
}

/*
 * 向 ObjectAddresses 数组添加条目。
 *
 * 与上面相同，但精确指定条目并提供一些“额外”数据。
 */
static void add_exact_object_address_extra(const ObjectAddress *fc_object,
							   const ObjectAddressExtra *fc_extra,
							   ObjectAddresses *fc_addrs)
{
	ObjectAddress *fc_item;
	ObjectAddressExtra *fc_itemextra;

	/* 如果第一次则分配额外空间 */
	if (!fc_addrs->extras)
		fc_addrs->extras = (ObjectAddressExtra *)
			palloc(fc_addrs->maxrefs * sizeof(ObjectAddressExtra));

	/* 如有需要则扩大数组 */
	if (fc_addrs->numrefs >= fc_addrs->maxrefs)
	{
		fc_addrs->maxrefs *= 2;
		fc_addrs->refs = (ObjectAddress *)
			repalloc(fc_addrs->refs, fc_addrs->maxrefs * sizeof(ObjectAddress));
		fc_addrs->extras = (ObjectAddressExtra *)
			repalloc(fc_addrs->extras, fc_addrs->maxrefs * sizeof(ObjectAddressExtra));
	}
	/* 记录此项 */
	fc_item = fc_addrs->refs + fc_addrs->numrefs;
	*fc_item = *fc_object;
	fc_itemextra = fc_addrs->extras + fc_addrs->numrefs;
	*fc_itemextra = *fc_extra;
	fc_addrs->numrefs++;
}

/*
 * 测试一个对象是否存在于 ObjectAddresses 数组中。
 *
 * 如果对象也是数组中某个对象的子对象，我们返回“true”。
 */
bool object_address_present(const ObjectAddress *fc_object,
					   const ObjectAddresses *fc_addrs)
{
	int			fc_i;

	for (fc_i = fc_addrs->numrefs - 1; fc_i >= 0; fc_i--)
	{
		const ObjectAddress *fc_thisobj = fc_addrs->refs + fc_i;

		if (fc_object->classId == fc_thisobj->classId &&
			fc_object->objectId == fc_thisobj->objectId)
		{
			if (fc_object->objectSubId == fc_thisobj->objectSubId ||
				fc_thisobj->objectSubId == 0)
				return true;
		}
	}

	return false;
}

/*
 * 与上面相同，除了如果对象存在，则也将给定的
 * 标志或入其相关的额外数据（必须存在）。
 */
static bool fc_object_address_present_add_flags(const ObjectAddress *fc_object,
								 int fc_flags,
								 ObjectAddresses *fc_addrs)
{
	bool		fc_result = false;
	int			fc_i;

	for (fc_i = fc_addrs->numrefs - 1; fc_i >= 0; fc_i--)
	{
		ObjectAddress *fc_thisobj = fc_addrs->refs + fc_i;

		if (fc_object->classId == fc_thisobj->classId &&
			fc_object->objectId == fc_thisobj->objectId)
		{
			if (fc_object->objectSubId == fc_thisobj->objectSubId)
			{
				ObjectAddressExtra *fc_thisextra = fc_addrs->extras + fc_i;

				fc_thisextra->flags |= fc_flags;
				fc_result = true;
			}
			else if (fc_thisobj->objectSubId == 0)
			{
				/*
				 * 如果我们在已经决定删除整个表后发现需要删除一列，
				 * 我们会到这里。显然，我们不再需要删除子对象，
				 * 所以报告我们在数组中找到了子对象。但不要将其
				 * 标志贴到整个对象上。
				 */
				fc_result = true;
			}
			else if (fc_object->objectSubId == 0)
			{
				/*
				 * 如果我们发现需要在已经决定删除某个列后删除整个表，
				 * 我们会到这里。我们不能报告整个对象在数组中，但
				 * 我们应该用整个对象的标志标记子对象。
				 *
				 * 似乎很吸引人物理地删除列的数组条目，或者至少标记它不再需要
				 * 单独删除。但这可能会导致，例如，在我们删除表之前
				 * 删除列的数据类型，这看起来并不是一个好主意。实际上，这是一个非常罕见的情况，
				 * 所以我们只接受进行单独的 DROP COLUMN 操作，尽管我们知道稍后
				 * 我们会删除表。
				 *
				 * 不过，我们可以将其标记为子对象，以便我们不会单独报告它，
				 * 这会引起混淆，因为它是不确定是否会发生。但要这么做
				 * 仅当标志 != 0（标志 == 0 是只读探测）。
				 *
				 * 因为数组中可能有这个对象的其他子对象，这种情况意味着我们
				 * 总是必须遍历整个数组；我们不能在匹配时提前退出。
				 */
				ObjectAddressExtra *fc_thisextra = fc_addrs->extras + fc_i;

				if (fc_flags)
					fc_thisextra->flags |= (fc_flags | DEPFLAG_SUBOBJECT);
			}
		}
	}

	return fc_result;
}

/*
 * 与上面相似，除了我们搜索一个 ObjectAddressStack。
 */
static bool fc_stack_address_present_add_flags(const ObjectAddress *fc_object,
								int fc_flags,
								ObjectAddressStack *fc_stack)
{
	bool		fc_result = false;
	ObjectAddressStack *fc_stackptr;

	for (fc_stackptr = fc_stack; fc_stackptr; fc_stackptr = fc_stackptr->next)
	{
		const ObjectAddress *fc_thisobj = fc_stackptr->object;

		if (fc_object->classId == fc_thisobj->classId &&
			fc_object->objectId == fc_thisobj->objectId)
		{
			if (fc_object->objectSubId == fc_thisobj->objectSubId)
			{
				fc_stackptr->flags |= fc_flags;
				fc_result = true;
			}
			else if (fc_thisobj->objectSubId == 0)
			{
				/*
				 * 我们正在访问具有整个表已经在堆栈上的列。与 object_address_present_add_flags() 一样，我们可以跳过对子对象的进一步处理，但我们不想将子对象的标志传播到整个对象。
				 */
				fc_result = true;
			}
			else if (fc_object->objectSubId == 0)
			{
				/*
				 * 我们正在访问一个已经在栈上的表列。正如在
				 * object_address_present_add_flags() 中，我们应该将
				 * 整个对象的标志传播到它的每个子对象。
				 */
				if (fc_flags)
					fc_stackptr->flags |= (fc_flags | DEPFLAG_SUBOBJECT);
			}
		}
	}

	return fc_result;
}

/*
 * 从一个 ObjectAddresses 数组记录多个依赖关系，在首先
 * 去除任何重复项之后。
 */
void record_object_address_dependencies(const ObjectAddress *fc_depender,
								   ObjectAddresses *fc_referenced,
								   DependencyType fc_behavior)
{
	fc_eliminate_duplicate_dependencies(fc_referenced);
	recordMultipleDependencies(fc_depender,
							   fc_referenced->refs, fc_referenced->numrefs,
							   fc_behavior);
}

/*
 * 对 ObjectAddresses 数组中的项进行排序。
 *
 * 主要排序关键字是 OID 降序，因此在大多数情况下较新的对象
 * 会首先列出。这主要有助于确保回归测试的稳定输出；
 * 如果对象的顺序是由用户输入确定的，例如 DROP 命令中的目标顺序，
 * 则不推荐这样做。
 */
void sort_object_addresses(ObjectAddresses *fc_addrs)
{
	if (fc_addrs->numrefs > 1)
		qsort((void *) fc_addrs->refs, fc_addrs->numrefs,
			  sizeof(ObjectAddress),
			  fc_object_address_comparator);
}

/*
 * 完成时清理 ObjectAddresses 数组。
 */
void free_object_addresses(ObjectAddresses *fc_addrs)
{
	pfree(fc_addrs->refs);
	if (fc_addrs->extras)
		pfree(fc_addrs->extras);
	pfree(fc_addrs);
}

/*
 * 确定由 objectAddress 标识的给定对象的类别。
 *
 * 该函数基本上是 object_classes[] 表的反向映射。
 * 我们将其实现为一个函数，因为 OID 不是连续的。
 */
ObjectClass
getObjectClass(const ObjectAddress *fc_object)
{
	/* 只有 pg_class 条目可以具有非零 objectSubId */
	if (fc_object->classId != RelationRelationId &&
		fc_object->objectSubId != 0)
		elog(ERROR, "invalid non-zero objectSubId for object class %u",
			 fc_object->classId);

	switch (fc_object->classId)
	{
		case RelationRelationId:
			/* 调用者必须检查 objectSubId */
			return OCLASS_CLASS;

		case ProcedureRelationId:
			return OCLASS_PROC;

		case TypeRelationId:
			return OCLASS_TYPE;

		case CastRelationId:
			return OCLASS_CAST;

		case CollationRelationId:
			return OCLASS_COLLATION;

		case ConstraintRelationId:
			return OCLASS_CONSTRAINT;

		case ConversionRelationId:
			return OCLASS_CONVERSION;

		case AttrDefaultRelationId:
			return OCLASS_DEFAULT;

		case LanguageRelationId:
			return OCLASS_LANGUAGE;

		case LargeObjectRelationId:
			return OCLASS_LARGEOBJECT;

		case OperatorRelationId:
			return OCLASS_OPERATOR;

		case OperatorClassRelationId:
			return OCLASS_OPCLASS;

		case OperatorFamilyRelationId:
			return OCLASS_OPFAMILY;

		case AccessMethodRelationId:
			return OCLASS_AM;

		case AccessMethodOperatorRelationId:
			return OCLASS_AMOP;

		case AccessMethodProcedureRelationId:
			return OCLASS_AMPROC;

		case RewriteRelationId:
			return OCLASS_REWRITE;

		case TriggerRelationId:
			return OCLASS_TRIGGER;

		case NamespaceRelationId:
			return OCLASS_SCHEMA;

		case StatisticExtRelationId:
			return OCLASS_STATISTIC_EXT;

		case TSParserRelationId:
			return OCLASS_TSPARSER;

		case TSDictionaryRelationId:
			return OCLASS_TSDICT;

		case TSTemplateRelationId:
			return OCLASS_TSTEMPLATE;

		case TSConfigRelationId:
			return OCLASS_TSCONFIG;

		case AuthIdRelationId:
			return OCLASS_ROLE;

		case DatabaseRelationId:
			return OCLASS_DATABASE;

		case TableSpaceRelationId:
			return OCLASS_TBLSPACE;

		case ForeignDataWrapperRelationId:
			return OCLASS_FDW;

		case ForeignServerRelationId:
			return OCLASS_FOREIGN_SERVER;

		case UserMappingRelationId:
			return OCLASS_USER_MAPPING;

		case DefaultAclRelationId:
			return OCLASS_DEFACL;

		case ExtensionRelationId:
			return OCLASS_EXTENSION;

		case EventTriggerRelationId:
			return OCLASS_EVENT_TRIGGER;

		case ParameterAclRelationId:
			return OCLASS_PARAMETER_ACL;

		case PolicyRelationId:
			return OCLASS_POLICY;

		case PublicationNamespaceRelationId:
			return OCLASS_PUBLICATION_NAMESPACE;

		case PublicationRelationId:
			return OCLASS_PUBLICATION;

		case PublicationRelRelationId:
			return OCLASS_PUBLICATION_REL;

		case SubscriptionRelationId:
			return OCLASS_SUBSCRIPTION;

		case TransformRelationId:
			return OCLASS_TRANSFORM;
	}

	/* 不应该到这里 */
	elog(ERROR, "unrecognized object class: %u", fc_object->classId);
	return OCLASS_CLASS;		/* 保持编译器安静 */
}

/*
 * 删除扩展对象的初始 ACL
 */
static void fc_DeleteInitPrivs(const ObjectAddress *fc_object)
{
	Relation	fc_relation;
	ScanKeyData fc_key[3];
	int			fc_nkeys;
	SysScanDesc fc_scan;
	HeapTuple	fc_oldtuple;

	fc_relation = table_open(InitPrivsRelationId, RowExclusiveLock);

	ScanKeyInit(&fc_key[0],
				Anum_pg_init_privs_objoid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_object->objectId));
	ScanKeyInit(&fc_key[1],
				Anum_pg_init_privs_classoid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_object->classId));
	if (fc_object->objectSubId != 0)
	{
		ScanKeyInit(&fc_key[2],
					Anum_pg_init_privs_objsubid,
					BTEqualStrategyNumber, F_INT4EQ,
					Int32GetDatum(fc_object->objectSubId));
		fc_nkeys = 3;
	}
	else
		fc_nkeys = 2;

	fc_scan = systable_beginscan(fc_relation, InitPrivsObjIndexId, true,
							  NULL, fc_nkeys, fc_key);

	while (HeapTupleIsValid(fc_oldtuple = systable_getnext(fc_scan)))
		CatalogTupleDelete(fc_relation, &fc_oldtuple->t_self);

	systable_endscan(fc_scan);

	table_close(fc_relation, RowExclusiveLock);
}
